Code

Ticket #3583: apach_auth.7.patch

File apach_auth.7.patch, 10.5 KB (added by peterd12, 6 years ago)

Updated to work with refactored django.dispatch. Applies cleanly against rev 8630.

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

     
    11from mod_python import apache 
    22import os 
     3from urllib import quote 
     4from django.core import signals 
     5from django.core.handlers.base import BaseHandler 
     6from django.core.handlers.modpython import ModPythonRequest 
     7from django.conf import settings 
     8from django.contrib.auth import authenticate, REDIRECT_FIELD_NAME 
     9from django.utils.encoding import iri_to_uri 
    310 
    4 def authenhandler(req, **kwargs): 
     11_str_to_bool = lambda s: s.lower() in ('1', 'true', 'on', 'yes') 
     12 
     13class ModPythonAuthOptions: 
     14    def __init__(self, req): 
     15        options = req.get_options() 
     16        self.permission_name = options.get('DjangoPermissionName', None) 
     17        self.staff_only = _str_to_bool(options.get('DjangoRequireStaffStatus', "on")) 
     18        self.superuser_only = _str_to_bool(options.get('DjangoRequireSuperuserStatus', "off")) 
     19        self.raise_forbidden = _str_to_bool(options.get('DjangoRaiseForbidden', "off")) 
     20        self.settings_module = options.get('DJANGO_SETTINGS_MODULE', None) 
     21 
     22def setup_environment(req, options): 
    523    """ 
    6     Authentication handler that checks against Django's auth database. 
     24    mod_python fakes the environ, and thus doesn't process SetEnv. This ensures 
     25    any future imports relying on settings will work. 
    726    """ 
    8  
    9     # mod_python fakes the environ, and thus doesn't process SetEnv.  This fixes 
    10     # that so that the following import works 
    1127    os.environ.update(req.subprocess_env) 
     28    if options.settings_module: 
     29        os.environ['DJANGO_SETTINGS_MODULE'] = options.settings_module 
    1230 
    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() 
     31def authenticate_user(user): 
     32    if user is None: 
     33        return False 
     34    if hasattr(user, 'is_authenticated') and not user.is_authenticated(): 
     35        return False 
     36    return True 
    1637 
    17     # check for PythonOptions 
    18     _str_to_bool = lambda s: s.lower() in ('1', 'true', 'on', 'yes') 
     38def validate_user(user, options): 
     39    if hasattr(user, 'is_active') and not user.is_active: 
     40        return False 
     41    if options.staff_only and not getattr(user, 'is_staff', None): 
     42        return False 
     43    if options.superuser_only and not getattr(user, 'is_superuser', None): 
     44        return False 
     45    # If a permission is required then user must have a has_perm function to 
     46    # validate. 
     47    if options.permission_name and (not hasattr(user, 'has_perm') or 
     48                                    not user.has_perm(options.permission_name)): 
     49        return False 
     50    return True 
    1951 
    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 
     52def redirect_to_login(req): 
     53    path = quote(req.uri) 
     54    if req.args: 
     55        path = '%s?%s' % (path, req.args) 
     56    path = quote(path) 
     57    iri = '%s?%s=%s' % (settings.LOGIN_URL, REDIRECT_FIELD_NAME, path) 
     58    uri = iri_to_uri(iri) 
     59    req.err_headers_out.add('Location', uri) 
     60    if req.proto_num >= 1001: 
     61        # Use HTTP Error 303 (see other) for HTTP/1.1 browsers. 
     62        raise apache.SERVER_RETURN, apache.HTTP_SEE_OTHER 
     63    else: 
     64        # Otherwise use HTTP Error 302 (moved temporarily). 
     65        raise apache.SERVER_RETURN, apache.HTTP_MOVED_TEMPORARILY 
    2766 
    28     from django.contrib.auth.models import User 
    29     from django import db 
    30     db.reset_queries() 
     67def authenhandler(req, **kwargs): 
     68    """ 
     69    mod_python authentication handler that checks against Django's auth 
     70    database. 
     71    """ 
     72    options = ModPythonAuthOptions(req) 
     73    setup_environment(req, options) 
    3174 
    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 
     75    signals.request_started.send(None) 
    3876    try: 
    39         try: 
    40             user = User.objects.get(**kwargs) 
    41         except User.DoesNotExist: 
     77        # This populates req.user too, so it's important to do first. 
     78        password = req.get_basic_auth_pw() 
     79 
     80        # Get the user from any of the installed backends. 
     81        user = authenticate(username=req.user, password=password) 
     82 
     83        if not authenticate_user(user): 
     84            # Raise unauthorized if the user doesn't authenticate to bring up a 
     85            # password dialog box to allow the user to authenticate. 
    4286            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 
     87 
     88        # Validate the user 
     89        if validate_user(user, options): 
     90            return apache.OK 
     91 
     92        # mod_python docs say that HTTP_FORBIDDEN should be raised if the user 
     93        # authenticates but doesn't validate but Django provides it as an 
     94        # option, alternately raising HTTP_UNAUTHORIZED again to provide the 
     95        # option of logging in as an alternate user. 
     96        if options.raise_forbidden: 
     97            return apache.HTTP_FORBIDDEN 
    5398        else: 
    54             return apache.HTTP_UNAUTHORIZED 
     99            return apache.HTTP_UNAUTHORIZED     
    55100    finally: 
    56         db.connection.close() 
     101        signals.request_finished.send(None) 
     102 
     103def accesshandler(req): 
     104    """ 
     105    mod_python access handler that uses the contrib.auth framework (with 
     106    sessions, therefore requiring a session cookie). 
     107    """ 
     108    options = ModPythonAuthOptions(req) 
     109    setup_environment(req, options) 
     110 
     111    # Set up middleware, now that settings works we can do it now. 
     112    base_handler = BaseHandler() 
     113    base_handler.load_middleware() 
     114 
     115    signals.request_started.send(None) 
     116    try: 
     117        request = ModPythonRequest(req) 
     118 
     119        # Apply request middleware 
     120        for middleware_method in base_handler._request_middleware: 
     121            response = middleware_method(request) 
     122            if response: 
     123                # If we get a response then there's no need to keep processing 
     124                # any remaining request middleware. 
     125                break 
     126 
     127        user = getattr(request, 'user', None) 
     128        if not authenticate_user(user): 
     129            # Rather than raising HTTP_UNAUTHORIZED (which the browser won't be 
     130            # able to handle since this isn't basic HTTP authentication), write 
     131            # a response which redirects to settings.LOGIN_URL 
     132            redirect_to_login(req) 
     133 
     134        if validate_user(user, options): 
     135            return apache.OK 
     136 
     137        # mod_python docs say that HTTP_FORBIDDEN should be raised if the user 
     138        # authenticates but doesn't validate but Django provides it as an 
     139        # option, alternately redirecting to login to provide the option of 
     140        # logging in as an alternate user. 
     141        if options.raise_forbidden: 
     142            return apache.HTTP_FORBIDDEN 
     143        else: 
     144            redirect_to_login(req) 
     145 
     146    finally: 
     147        signals.request_finished.send(None) 
  • 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