"""
elevate.utils
~~~~~~~~~~~~~

:copyright: (c) 2017-present by Justin Mayer.
:copyright: (c) 2014-2016 by Matt Robenolt.
:license: BSD, see LICENSE for more details.
"""
from django.core.signing import BadSignature
from django.utils import http
from django.utils.crypto import get_random_string, constant_time_compare
import django

from elevate.settings import COOKIE_NAME, COOKIE_AGE, COOKIE_SALT


def grant_elevated_privileges(request, max_age=COOKIE_AGE):
    """
    Assigns a random token to the user's session
    that allows them to have elevated permissions
    """
    user = getattr(request, 'user', None)

    # If there's not a user on the request, just noop
    if user is None:
        return

    if not is_authenticated(user):
        raise ValueError('User needs to be logged in to be elevated')

    # Token doesn't need to be unique,
    # just needs to be unpredictable and match the cookie and the session
    token = get_random_string()
    request.session[COOKIE_NAME] = token
    request._elevate = True
    request._elevate_token = token
    request._elevate_max_age = max_age
    return token


def revoke_elevated_privileges(request):
    """
    Revoke elevated privileges from a request explicitly
    """
    request._elevate = False
    if COOKIE_NAME in request.session:
        del request.session[COOKIE_NAME]


def has_elevated_privileges(request):
    """
    Check if a request is allowed to perform Elevate actions
    """
    if getattr(request, '_elevate', None) is None:
        try:
            request._elevate = (
                is_authenticated(request.user) and
                constant_time_compare(
                    request.get_signed_cookie(COOKIE_NAME, salt=COOKIE_SALT, max_age=COOKIE_AGE),
                    request.session[COOKIE_NAME]
                )
            )
        except (KeyError, BadSignature):
            request._elevate = False
    return request._elevate


def is_authenticated(user):
    """
    Check if a user is authenticated

    In Django 1.10 User.is_authenticated was changed to a property and
    backwards compatible support for is_authenticated being callable was
    finally removed in Django 2.0. This function can be removed once support
    Django versions earlier than 1.10 are dropped.
    """
    if callable(user.is_authenticated):
        return user.is_authenticated()
    else:
        return user.is_authenticated


def is_safe_url(url, allowed_hosts, require_https=False):
    """
    Wrapper around is_safe_url for Django versions < 1.11
    """
    if django.VERSION >= (1, 11):
        return http.is_safe_url(url, allowed_hosts=allowed_hosts, require_https=require_https)
    else:
        return http.is_safe_url(url, allowed_hosts[0])