Source code for smarter.common.utils.request

"""
smarter.common.utils.request
=============================

Helpers for request authentication detection in Django and DRF.

This module provides the ``is_authenticated_request`` function, which determines whether a given
request object is authenticated. It supports Django's ``HttpRequest``, Django REST Framework's ``Request``,
and ASGIRequest types, and provides extensive logging for debugging and auditing purposes.

**Example usage:**

.. code-block:: python

    from smarter.common.utils import is_authenticated_request
    from django.http import HttpRequest

    request = HttpRequest()
    request.user = SomeUserObject()
    authenticated = is_authenticated_request(request)
    print(authenticated)  # True or False depending on user.is_authenticated

"""

import random
from typing import TYPE_CHECKING, Optional, Union

from smarter.common.const import SMARTER_IS_INTERNAL_API_REQUEST
from smarter.lib import logging

from .uri import smarter_build_absolute_uri

if TYPE_CHECKING:
    from django.core.handlers.asgi import ASGIRequest
    from django.http import HttpRequest
    from rest_framework.request import Request

logger = logging.getLogger(__name__)
logger_prefix = logging.formatted_text(__name__)
RequestType = Union["HttpRequest", "Request", "ASGIRequest"]


[docs] def is_authenticated_request(request: Optional[RequestType]) -> bool: """ Determines whether the provided request is authenticated. Provides extensive logging for debugging purposes. :param request: The request object to check. This can be an instance of :class:`django.http.HttpRequest`, :class:`rest_framework.request.Request`, or :class:`django.core.handlers.wsgi.ASGIRequest`. If ``None`` is provided, the function will return ``False``. :type request: Optional[Union[HttpRequest, Request, ASGIRequest]] :return: Returns ``True`` if the request is authenticated (i.e., the request has a ``user`` attribute and ``user.is_authenticated`` is ``True``). Returns ``False`` otherwise. :rtype: bool :raises Exception: Any unexpected error during attribute access will be caught and logged; the function will return ``False`` in such cases. .. note:: This function is compatible with Django and Django REST Framework request objects. It also supports ASGIRequest and can be used in unit tests with mock objects that have the required attributes. .. warning:: If the request object does not have a ``user`` attribute, or if ``user.is_authenticated`` is not available, the function will return ``False``. Any exceptions are logged as warnings. **Example usage:** .. code-block:: python from smarter.common.utils import is_authenticated_request from django.http import HttpRequest request = HttpRequest() request.user = SomeUserObject() authenticated = is_authenticated_request(request) print(authenticated) # True or False depending on user.is_authenticated .. code-block:: python # Example with DRF Request from rest_framework.request import Request drf_request = Request(...) authenticated = is_authenticated_request(drf_request) print(authenticated) """ # pylint: disable=import-outside-toplevel from django.core.handlers.asgi import ASGIRequest from django.http import HttpRequest from rest_framework.request import Request from smarter.lib.cache import cache_results if isinstance(request, (HttpRequest, Request, ASGIRequest)): path = request.path if hasattr(request, "path") else "unknown_path" method = request.method if hasattr(request, "method") else "unknown_method" username = getattr(getattr(request, "user", None), "username", "unknown_user") cache_key = f"{logger_prefix}.is_authenticated_request.{method}_{path}_{username}" else: cache_key = f"{logger_prefix}.is_authenticated_request.invalid_request_{random.randint(1, 1000000)}" @cache_results(cache_key=cache_key) def cached_is_authenticated_request() -> bool: try: if not isinstance(request, (HttpRequest, Request, ASGIRequest)): logger.warning( "%s.is_authenticated_request() - Invalid request type: %s, expected HttpRequest, Request, or ASGIRequest", logger_prefix, type(request), ) return False is_valid_request_object = isinstance(request, (HttpRequest, Request, ASGIRequest)) if not is_valid_request_object: # suggests buggy code, hence the warning logger.warning( "%s.is_authenticated_request() Invalid request object of type %s - returning False", logger_prefix, type(request), ) return False has_user = hasattr(request, "user") if not has_user: logger.debug( "%s.is_authenticated_request() Request does not have 'user' attribute - returning False", logger_prefix, ) return False has_is_authenticated = has_user and hasattr(request.user, "is_authenticated") if not has_is_authenticated: # this should not happen in normal code, hence the warning logger.warning( "%s.is_authenticated_request() Request.user of type %s does not have 'is_authenticated' attribute - returning False", logger_prefix, type(request.user), ) return False url = smarter_build_absolute_uri(request) if is_valid_request_object and has_user and has_is_authenticated: retval = request.user.is_authenticated if retval: return True else: logger.debug( "%s.is_authenticated_request() Request is not authenticated - returning False URL: %s", logger_prefix, url, ) return False if hasattr(request, SMARTER_IS_INTERNAL_API_REQUEST): logger.debug( "%s.is_authenticated_request() Request has SMARTER_IS_INTERNAL_API_REQUEST=%s", logger_prefix, getattr(request, SMARTER_IS_INTERNAL_API_REQUEST, False), ) # check request head for Authorization if hasattr(request, "headers") and request.headers is not None: auth_header = request.headers.get("Authorization") if auth_header: logger.debug( "%s.is_authenticated_request() Request has Authorization header (first 4 chars): %s", logger_prefix, str(auth_header)[:4], ) else: logger.debug( "%s.is_authenticated_request() Request does not have Authorization header", logger_prefix, ) return retval return False # pylint: disable=W0718 except Exception as e: logger.error("%s.is_authenticated_request() failed: %s", logger_prefix, logging.formatted_text(str(e))) return False return cached_is_authenticated_request()
__all__ = ["is_authenticated_request"]