Ticket #3583: apache_auth.5.patch
File apache_auth.5.patch, 10.7 KB (added by , 17 years ago) |
---|
-
django/contrib/auth/handlers/modpython.py
1 1 from mod_python import apache 2 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 3 11 4 def authenhandler(req, **kwargs): 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): 5 24 """ 6 Authentication handler that checks against Django's auth database. 25 mod_python fakes the environ, and thus doesn't process SetEnv. This ensures 26 any future imports relying on settings will work. 7 27 """ 8 9 # mod_python fakes the environ, and thus doesn't process SetEnv. This fixes10 # that so that the following import works11 28 os.environ.update(req.subprocess_env) 29 if options.settings_module: 30 os.environ['DJANGO_SETTINGS_MODULE'] = options.settings_module 12 31 13 # apache 2.2 requires a call to req.get_basic_auth_pw() before 14 # req.user and friends are available. 15 req.get_basic_auth_pw() 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 16 38 17 # check for PythonOptions 18 _str_to_bool = lambda s: s.lower() in ('1', 'true', 'on', 'yes') 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 19 52 20 options = req.get_options() 21 permission_name = options.get('DjangoPermissionName', None) 22 staff_only = _str_to_bool(options.get('DjangoRequireStaffStatus', "on")) 23 superuser_only = _str_to_bool(options.get('DjangoRequireSuperuserStatus', "off")) 24 settings_module = options.get('DJANGO_SETTINGS_MODULE', None) 25 if settings_module: 26 os.environ['DJANGO_SETTINGS_MODULE'] = settings_module 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 27 67 28 from django.contrib.auth.models import User 29 from django import db 30 db.reset_queries() 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) 31 75 32 # check that the username is valid 33 kwargs = {'username': req.user, 'is_active': True} 34 if staff_only: 35 kwargs['is_staff'] = True 36 if superuser_only: 37 kwargs['is_superuser'] = True 76 dispatcher.send(signal=signals.request_started) 38 77 try: 39 try: 40 user = User.objects.get(**kwargs) 41 except User.DoesNotExist: 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. 42 87 return apache.HTTP_UNAUTHORIZED 43 44 # check the password and any permission given 45 if user.check_password(req.get_basic_auth_pw()): 46 if permission_name: 47 if user.has_perm(permission_name): 48 return apache.OK 49 else: 50 return apache.HTTP_UNAUTHORIZED 51 else: 52 return apache.OK 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 53 99 else: 54 return apache.HTTP_UNAUTHORIZED 100 return apache.HTTP_UNAUTHORIZED 55 101 finally: 56 db.connection.close() 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) -
docs/apache_auth.txt
16 16 Configuring Apache 17 17 ================== 18 18 19 To check against Django's authorization database from a Apache configuration 20 file, you'll need to use mod_python's ``PythonAuthenHandler`` directive along 21 with the standard ``Auth*`` and ``Require`` directives:: 19 To check against Django's authorization database from an Apache configuration 20 file, you can either use mod_python's ``PythonAccessHandler`` directive or 21 the ``PythonAuthenHandler`` directive along with the standard ``Auth*`` and 22 ``Require`` directives. 22 23 24 The ``PythonAccessHandler`` directive validates using the built-in 25 ``contrib.auth`` authentication, which uses the session cookie and redirects to 26 the ``settings.LOGIN_URL`` if authentication is required:: 27 23 28 <Location /example/> 29 SetEnv DJANGO_SETTINGS_MODULE mysite.settings 30 PythonAccessHandler django.contrib.auth.handlers.modpython 31 </Location> 32 33 The ``PythonAuthenHandler`` directive just uses basic HTTP authentication:: 34 35 <Location /example/> 24 36 AuthType Basic 25 37 AuthName "example.com" 26 38 Require valid-user … … 72 84 PythonAuthenHandler django.contrib.auth.handlers.modpython 73 85 </Location> 74 86 75 By default, the authentication handler will limit access to the ``/example/``76 location to users marked as staff members. You can use a set of87 By default, the authentication handler examples above will limit access to the 88 ``/example/`` location to users marked as staff members. You can use a set of 77 89 ``PythonOption`` directives to modify this behavior: 78 90 79 91 ================================ ========================================= … … 97 109 98 110 By default no specific permission will be 99 111 required. 112 113 ``DjangoRaiseForbidden`` If the user authenticates but does not 114 have the valid credentials, raise HTTP 115 Error 403 (forbidden) rather providing a 116 login prompt again. 117 118 Defaults to ``off``. 100 119 ================================ ========================================= 101 120 121 You may also want to make Apache pass the correct headers to stop proxy servers 122 (and local browser caches) from caching your protected resources. Assuming that 123 the ``mod_expires`` and ``mod_headers`` modules are enabled on your Apache 124 server, you can add the following directives to your ``Location`` block:: 125 126 # Stop resources from being cached 127 ExpiresActive On 128 ExpiresDefault A0 129 Header append Cache-Control: "no-cache, no-store, must-revalidate, private" 130 102 131 Note that sometimes ``SetEnv`` doesn't play well in this mod_python 103 132 configuration, for reasons unknown. If you're having problems getting 104 133 mod_python to recognize your ``DJANGO_SETTINGS_MODULE``, you can set it using