Code

Ticket #3583: apache_auth.6.patch

File apache_auth.6.patch, 10.6 KB (added by peterd12, 6 years ago)

Modified to apply cleanly against rev. 8584

  • django/contrib/auth/handlers/modpython.py

     
    11from mod_python import apache 
    22import 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 
    311 
    4 def authenhandler(req, **kwargs): 
     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): 
    524    """ 
    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. 
    727    """ 
    8  
    9     # mod_python fakes the environ, and thus doesn't process SetEnv.  This fixes 
    10     # that so that the following import works 
    1128    os.environ.update(req.subprocess_env) 
     29    if options.settings_module: 
     30        os.environ['DJANGO_SETTINGS_MODULE'] = options.settings_module 
    1231 
    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() 
     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 
    1638 
    17     # check for PythonOptions 
    18     _str_to_bool = lambda s: s.lower() in ('1', 'true', 'on', 'yes') 
     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 
    1952 
    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 
     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 
    2767 
    28     from django.contrib.auth.models import User 
    29     from django import db 
    30     db.reset_queries() 
     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) 
    3175 
    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) 
    3877    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. 
    4287            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 
    5399        else: 
    54             return apache.HTTP_UNAUTHORIZED 
     100            return apache.HTTP_UNAUTHORIZED     
    55101    finally: 
    56         db.connection.close() 
     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) 
  • docs/howto/apache-auth.txt

     
    2222Configuring Apache 
    2323================== 
    2424 
    25 To check against Django's authorization database from a Apache configuration 
    26 file, you'll need to use mod_python's ``PythonAuthenHandler`` directive along 
    27 with the standard ``Auth*`` and ``Require`` directives: 
     25To check against Django's authorization database from an Apache configuration 
     26file, you can either use mod_python's ``PythonAccessHandler`` directive or  
     27the ``PythonAuthenHandler`` directive along with the standard ``Auth*`` and 
     28``Require`` directives. 
    2829 
     30The ``PythonAccessHandler`` directive validates using the built-in 
     31``contrib.auth`` authentication, which uses the session cookie and redirects to 
     32the ``settings.LOGIN_URL`` if authentication is required:: 
     33 
    2934.. code-block:: apache 
    3035 
    3136    <Location /example/> 
     37        SetEnv DJANGO_SETTINGS_MODULE mysite.settings 
     38        PythonAccessHandler django.contrib.auth.handlers.modpython 
     39    </Location> 
     40 
     41The ``PythonAuthenHandler`` directive just uses basic HTTP authentication:: 
     42 
     43.. code-block:: apache 
     44 
     45    <Location /example/> 
    3246        AuthType Basic 
    3347        AuthName "example.com" 
    3448        Require valid-user 
     
    110124 
    111125                                      By default no specific permission will be 
    112126                                      required. 
     127 
     128    ``DjangoRaiseForbidden``          If the user authenticates but does not 
     129                                      have the valid credentials, raise HTTP 
     130                                      Error 403 (forbidden) rather providing a 
     131                                      login prompt again. 
     132 
     133                                      Defaults to ``off``. 
    113134    ================================  ========================================= 
    114135 
     136You may also want to make Apache pass the correct headers to stop proxy servers 
     137(and local browser caches) from caching your protected resources. Assuming that 
     138the ``mod_expires`` and ``mod_headers`` modules are enabled on your Apache 
     139server, you can add the following directives to your ``Location`` block:: 
     140 
     141    # Stop resources from being cached 
     142    ExpiresActive On 
     143    ExpiresDefault A0 
     144    Header append Cache-Control: "no-cache, no-store, must-revalidate, private" 
     145 
    115146Note that sometimes ``SetEnv`` doesn't play well in this mod_python 
    116147configuration, for reasons unknown. If you're having problems getting 
    117148mod_python to recognize your ``DJANGO_SETTINGS_MODULE``, you can set it using 
     
    120151 
    121152    SetEnv DJANGO_SETTINGS_MODULE mysite.settings 
    122153    PythonOption DJANGO_SETTINGS_MODULE mysite.settings 
     154 
     155.. _authentication system: ../authentication/ 
     156.. _Subversion: http://subversion.tigris.org/ 
     157.. _mod_dav: http://httpd.apache.org/docs/2.0/mod/mod_dav.html 
     158.. _custom permissions: ../authentication/#custom-permissions 
     159