from mod_python import apache
import os
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.contrib.auth import authenticate

_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.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, options):
    if not user:
        return False
    # Require an is_authenticated property and check it
    if not hasattr(user, 'is_authenticated') or not user.is_authenticated():
        return False
    return True

def validate_user(user, options):
    # Don't require an is_active property, but if it's there then check it
    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 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)

        # Raise unauthorized if the user wasn't authenticated to bring up
        # a password dialog box to allow the user to authenticate.

        # Check authentification of the user
        if not authenticate_user(user, options):
            return apache.HTTP_UNAUTHORIZED

        # Validate and check permission of the user
        if validate_user(user, options):
            return apache.OK
        else:
            # mod_python docs say that HTTP_FORBIDDEN should be raised if the
            # user authenticates but doesn't validate.
            return apache.HTTP_FORBIDDEN
    finally:
        dispatcher.send(signal=signals.request_finished)

def accesshandler(req):
    """
    mod_python access handler that uses the contrib.auth framework (with
    sessions and 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, we should probably stop processing any
                # remaining request middleware.
                break

        # get user object
        user = getattr(request, 'user', None)

        # Check authentification of the user
        if not authenticate_user(user, options):
            return apache.HTTP_UNAUTHORIZED

        # Validate and check permission of the user
        if validate_user(user, options):
            return apache.OK
        else:
            # mod_python docs say that HTTP_FORBIDDEN should be raised if the
            # user authenticates but doesn't validate.
            return apache.HTTP_FORBIDDEN

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