web-dev-qa-db-fra.com

Django et Middleware qui utilisent request.user sont toujours anonymes

J'essaie de créer un middleware qui modifie certains champs pour l'utilisateur en fonction du sous-domaine, etc.

Le seul problème est que request.user entre toujours en tant qu'utilisateur anonyme dans le middleware, mais qu'il s'agit bien de l'utilisateur approprié dans les vues. J'ai laissé l'authentification par défaut et le middleware de session que Django utilise dans les paramètres.

Il y a une question similaire ici: Django, request.user est toujours un utilisateur anonyme Mais ne répond pas excessivement à la question car je n'utilise pas différentes méthodes d'authentification, et l'authentification djangos est en cours d'exécution avant J'appelle mon propre middleware.

Existe-t-il un moyen, lorsque vous utilisez DRF, d’obtenir le request.user dans le middleware? Je vais montrer un exemple de code ici:

class SampleMiddleware(object):

  def process_view(self, request, view_func, view_args, view_kwargs):
    #This will be AnonymousUser.  I need it to be the actual user making the request.
    print (request.user)    

  def process_response(self, request, response):
    return response

avec process_request:

class SampleMiddleware(object):

  def process_request(self, request):
    #This will be AnonymousUser.  I need it to be the actual user making the request.
    print (request.user)    

  def process_response(self, request, response):
    return response

Merci!

Jordan

21
Jordan

Salut les gars, j'ai résolu ce problème en récupérant le jeton DRF des demandes et en chargeant request.user vers l'utilisateur associé à ce modèle. 

J'avais l'authentification Django et le middleware de session par défaut, mais il semble que DRF utilisait son authentification jeton après middleware pour résoudre l'utilisateur (toutes les demandes étaient des demandes CORS, c'est peut-être pour cela). Voici ma classe middleware mise à jour:

from re import sub
from rest_framework.authtoken.models import Token
from core.models import OrganizationRole, Organization, User

class OrganizationMiddleware(object):

  def process_view(self, request, view_func, view_args, view_kwargs):
    header_token = request.META.get('HTTP_AUTHORIZATION', None)
    if header_token is not None:
      try:
        token = sub('Token ', '', request.META.get('HTTP_AUTHORIZATION', None))
        token_obj = Token.objects.get(key = token)
        request.user = token_obj.user
      except Token.DoesNotExist:
        pass
    #This is now the correct user
    print (request.user)

Ceci peut être utilisé sur process_view ou process_request également.

J'espère que cela pourra aider quelqu'un à l'avenir.

22
Jordan

Je suis tombé sur cette affaire aujourd'hui tout en ayant le même problème.

TL; DR;

Passer ci-dessous pour un exemple de code


Explication

Le fait est que les DRF ont leur propre flux de choses, en plein milieu de la requête Django cycle de vie .

Donc, si le flux de middleware normal est:

  1. request_middleware (avant de commencer à travailler sur la requête)
  2. view_middleware (avant d'appeler la vue)
  3. template_middleware (avant le rendu)
  4. response_middleware (avant la réponse finale)

Le code DRF remplace le code de la vue Django par défaut et exécute leur propre code .

Dans le lien ci-dessus, vous pouvez voir qu'ils enveloppent la demande d'origine avec leurs propres méthodes, l'une de ces méthodes étant l'authentification DRF.

Revenons donc à votre question. C’est la raison pour laquelle utiliser request.user dans un middleware est prématuré, car il n’obtient que sa valeur after / view_middleware ** s’exécute.

La solution à laquelle j’ai opté est d’avoir mon intergiciel de définir une LazyObject. Cela aide, car mon code (l’APiVIew du DRF) s’exécute lorsque l’utilisateur réel est déjà défini par Authentification de DRF . Cette solution a été proposée ici avec une discussion.

Cela aurait peut-être été mieux si DRF disposait d'un meilleur moyen d'étendre ses fonctionnalités, mais, dans l'état actuel des choses, cela semble meilleur que la solution fournie (en termes de performances et de lisibilité).


Exemple de code

from Django.utils.functional import SimpleLazyObject

def get_actual_value(request):
    if request.user is None:
        return None

    return request.user #here should have value, so any code using request.user will work


class MyCustomMiddleware(object):
    def process_request(self, request):
        request.custom_prop = SimpleLazyObject(lambda: get_actual_value(request))
13
Daniel Dubovski

Basé sur la solution très élégante de Daniel Dubovski ci-dessus, voici un exemple de middleware pour Django 1.11:

from Django.utils.functional import SimpleLazyObject
from organization.models import OrganizationMember
from Django.core.exceptions import ObjectDoesNotExist


def get_active_member(request):
    try:
        active_member = OrganizationMember.objects.get(user=request.user)
    except (ObjectDoesNotExist, TypeError):
        active_member = None
    return active_member


class OrganizationMiddleware(object):
    def __init__(self, get_response):
        self.get_response = get_response


    def __call__(self, request):
        # Code to be executed for each request before
        # the view (and later middleware) are called.

        request.active_member = SimpleLazyObject(lambda: get_active_member(request))

        response = self.get_response(request)

        # Code to be executed for each request/response after
        # the view is called.
        return response
4
Sune Kjærgård

Je sais que cela ne répond pas exactement à la question "pouvons-nous accéder à cela à partir du middleware", mais je pense que c'est une solution plus élégante que celle qui fait le même travail dans le middleware que ce que DRJ fait dans sa base view class . Au moins pour ce dont j'avais besoin, il était plus logique d'ajouter ici. 

Fondamentalement, je remplace simplement la méthode 'perform_authentication ()' du code de DRF, car je devais ajouter plus d'éléments liés à l'utilisateur actuel dans la demande. La méthode appelait à l'origine 'request.user'.

class MyGenericViewset(viewsets.GenericViewSet):

    def perform_authentication(self, request):
        request.user

        if request.user and request.user.is_authenticated():
            request.my_param1 = 'whatever'

Après cela dans vos propres vues, au lieu de définir les paramètres APIView de DRF en tant que classe parent, définissez simplement cette classe en tant que parent.

2
mrmuggles

La solution de Daniel Dubovski est probablement la meilleure dans la plupart des cas.

Le problème avec l'approche paresseux est si vous devez compter sur les effets secondaires. Dans mon cas, il faut que quelque chose se passe pour chaque demande, quoi qu'il arrive.

Si j'utilisais une valeur spéciale telle que request.custom_prop, elle devait être évaluée pour chaque demande afin que les effets secondaires se produisent. J'ai remarqué que d'autres personnes définissent request.user , mais cela ne fonctionne pas pour moi car un middleware ou une classe d'authentification remplace cette propriété.

Et si DRF supportait son propre middleware? Où pourrais-je le brancher? Le moyen le plus simple dans mon cas (je n'ai pas besoin d'accéder à l'objet request, mais à l'utilisateur authentifié) semble être de se connecter à la classe d'authentification elle-même:

from rest_framework.authentication import TokenAuthentication

class TokenAuthenticationWithSideffects(TokenAuthentication):

    def authenticate(self, request):
        user_auth_Tuple = super().authenticate(request)

        if user_auth_Tuple is None:
            return
        (user, token) = user_auth_Tuple

        # Do stuff with the user here!

        return (user, token)

Ensuite, je pourrais simplement remplacer cette ligne dans mes paramètres:

REST_FRAMEWORK = {
    "DEFAULT_AUTHENTICATION_CLASSES": (
        #"rest_framework.authentication.TokenAuthentication",
        "my_project.authentication.TokenAuthenticationWithSideffects",
    ),
    # ...
}

Je ne fais pas la promotion de cette solution, mais cela aidera peut-être quelqu'un d'autre.

Avantages:

  • Il résout ce problème spécifique
  • Il n'y a pas de double authentification
  • Facile à maintenir

Inconvénients:

  • Non testé en production
  • Les choses se passent dans un endroit inattendu
  • Effets secondaires...
0
André Laszlo

Je n'étais pas très satisfait des solutions proposées. Voici une solution qui utilise des éléments internes DRF pour s’assurer que l’authentification correcte est appliquée dans le middleware, même si la vue dispose de classes d’autorisations spécifiques. Il utilise le hook middleware process_view qui nous donne accès à la vue que nous sommes sur le point d'exploiter:

class CustomTenantMiddleware():
    def process_view(self, request, view_func, view_args, view_kwargs):
        # DRF saves the class of the view function as the .cls property
        view_class = view_func.cls
        try:
            # We need to instantiate the class
            view = view_class()
            # And give it an action_map. It's not relevant for us, but otherwise it errors.
            view.action_map = {}
            # Here's our fully formed and authenticated (or not, depending on credentials) request
            request = view.initialize_request(request)
        except (AttributeError, TypeError):
            # Can't initialize the request from this view. Fallback to using default permission classes
            request = APIView().initialize_request(request)

        # Here the request is fully formed, with the correct permissions depending on the view.

Notez que cela n'évite pas d'avoir à s'authentifier deux fois. La DRF continuera à s’authentifier avec plaisir juste après.

0
Gustav Wengel