from mod_python import apache
import os
from urllib import quote
from django.core import signals
from django.dispatch import dispatcher
from django.core.handlers.base import BaseHandler
from django.core.handlers.modpython import ModPythonRequest
from django.conf import settings
from django.contrib.auth import authenticate, REDIRECT_FIELD_NAME
from django.utils.encoding import iri_to_uri

_str_to_bool = lambda s: s.lower() in ('1', 'true', 'on', 'yes')

class ModPythonAuthOptions:
    def __init__(self, req):
        options = req.get_options()
        self.permission_name = options.get('DjangoPermissionName', None)
        self.staff_only = _str_to_bool(options.get('DjangoRequireStaffStatus', "on"))
        self.superuser_only = _str_to_bool(options.get('DjangoRequireSuperuserStatus', "off"))
        self.raise_forbidden = _str_to_bool(options.get('DjangoRaiseForbidden', "off"))
        self.settings_module = options.get('DJANGO_SETTINGS_MODULE', None)

def setup_environment(req, options):
    """
    mod_python fakes the environ, and thus doesn't process SetEnv. This ensures
    any future imports relying on settings will work.
    """
    os.environ.update(req.subprocess_env)
    if options.settings_module:
        os.environ['DJANGO_SETTINGS_MODULE'] = options.settings_module

def authenticate_user(user):
    if user is None:
        return False
    if hasattr(user, 'is_authenticated') and not user.is_authenticated():
        return False
    return True

def validate_user(user, options):
    if hasattr(user, 'is_active') and not user.is_active:
        return False
    if options.staff_only and not getattr(user, 'is_staff', None):
        return False
    if options.superuser_only and not getattr(user, 'is_superuser', None):
        return False
    # If a permission is required then user must have a has_perm function to
    # validate.
    if options.permission_name and (not hasattr(user, 'has_perm') or
                                    not user.has_perm(options.permission_name)):
        return False
    return True

def redirect_to_login(req):
    path = quote(req.uri)
    if req.args:
        path = '%s?%s' % (path, req.args)
    path = quote(path)
    iri = '%s?%s=%s' % (settings.LOGIN_URL, REDIRECT_FIELD_NAME, path)
    uri = iri_to_uri(iri)
    req.err_headers_out.add('Location', uri)
    if req.proto_num >= 1001:
        # Use HTTP Error 303 (see other) for HTTP/1.1 browsers.
        raise apache.SERVER_RETURN, apache.HTTP_SEE_OTHER
    else:
        # Otherwise use HTTP Error 302 (moved temporarily).
        raise apache.SERVER_RETURN, apache.HTTP_MOVED_TEMPORARILY

def authenhandler(req, **kwargs):
    """
    mod_python authentication handler that checks against Django's auth
    database.
    """
    options = ModPythonAuthOptions(req)
    setup_environment(req, options)

    dispatcher.send(signal=signals.request_started)
    try:
        # This populates req.user too, so it's important to do first.
        password = req.get_basic_auth_pw()

        # Get the user from any of the installed backends.
        user = authenticate(username=req.user, password=password)

        if not authenticate_user(user):
            # Raise unauthorized if the user doesn't authenticate to bring up a
            # password dialog box to allow the user to authenticate.
            return apache.HTTP_UNAUTHORIZED

        # Validate the user
        if validate_user(user, options):
            return apache.OK

        # mod_python docs say that HTTP_FORBIDDEN should be raised if the user
        # authenticates but doesn't validate but Django provides it as an
        # option, alternately raising HTTP_UNAUTHORIZED again to provide the
        # option of logging in as an alternate user.
        if options.raise_forbidden:
            return apache.HTTP_FORBIDDEN
        else:
            return apache.HTTP_UNAUTHORIZED    
    finally:
        dispatcher.send(signal=signals.request_finished)

def accesshandler(req):
    """
    mod_python access handler that uses the contrib.auth framework (with
    sessions, therefore requiring a session cookie).
    """
    options = ModPythonAuthOptions(req)
    setup_environment(req, options)

    # Set up middleware, now that settings works we can do it now.
    base_handler = BaseHandler()
    base_handler.load_middleware()

    dispatcher.send(signal=signals.request_started)
    try:
        request = ModPythonRequest(req)

        # Apply request middleware
        for middleware_method in base_handler._request_middleware:
            response = middleware_method(request)
            if response:
                # If we get a response then there's no need to keep processing
                # any remaining request middleware.
                break

        user = getattr(request, 'user', None)
        if not authenticate_user(user):
            # Rather than raising HTTP_UNAUTHORIZED (which the browser won't be
            # able to handle since this isn't basic HTTP authentication), write
            # a response which redirects to settings.LOGIN_URL
            redirect_to_login(req)

        if validate_user(user, options):
            return apache.OK

        # mod_python docs say that HTTP_FORBIDDEN should be raised if the user
        # authenticates but doesn't validate but Django provides it as an
        # option, alternately redirecting to login to provide the option of
        # logging in as an alternate user.
        if options.raise_forbidden:
            return apache.HTTP_FORBIDDEN
        else:
            redirect_to_login(req)

    finally:
        dispatcher.send(signal=signals.request_finished)
