Ticket #3583: apache_auth.6.patch

File apache_auth.6.patch, 10.6 KB (added by peterd12, 7 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
Back to Top