Django-rest-framework: DRF devrait autoriser toutes les requêtes OPTIONS par défaut

Créé le 21 nov. 2017  ·  22Commentaires  ·  Source: encode/django-rest-framework

Suite à la discussion sur le #908

Les autorisations pour les requêtes OPTIONS (métadonnées) sont gérées de manière incorrecte dans DRF.

Conformément aux spécifications du OPTIONS ne sont PAS authentifiées, ce qui signifie que les utilisateurs recevront toujours une erreur 401 lors du contrôle en amont d'une demande de points de terminaison authentifiés, car les navigateurs modernes n'envoient jamais cela conformément aux spécifications :

Sinon, faites une demande de contrôle en amont. Récupérez l'URL de la demande à partir de la source d'origine à l'aide de la source de référence comme source de référence de remplacement avec l'indicateur de redirection manuelle et l'indicateur de blocage des cookies définis, à l'aide de la méthode OPTIONS , et avec les contraintes supplémentaires suivantes :

  • Incluez un en-tête Access-Control-Request-Method avec comme valeur de champ d'en-tête la méthode de demande (même s'il s'agit d'une méthode simple).
  • Si les en-têtes de demande d'auteur ne sont pas vides, incluez un en-tête Access-Control-Request-Headers avec comme valeur de champ d'en-tête une liste séparée par des virgules des noms de champ d'en-tête des en-têtes de demande d'auteur dans l'ordre lexicographique, chacun converti en minuscules ASCII (même lorsqu'un ou plus sont un simple en-tête).
  • Excluez les en-têtes de demande d'auteur.
  • ➡️ Exclure les informations d'identification de l'utilisateur .
  • Excluez le corps de l'entité de demande.

Je pense que cela devrait être le comportement par défaut ici.
DRF doit autoriser toutes les demandes OPTIONS par défaut pour les classes d'autorisation standard (IsAuthenticated, IsAdminUser, etc.) et les utilisateurs peuvent annuler cela lorsqu'ils ont explicitement besoin de protéger les informations de métadonnées (enfreignant la compatibilité CORS standard)

Étapes à reproduire :

vues.py

class MyView(APIView):

    permission_classes = (IsAuthenticated,)

URL.py

urlpatterns = [
    url(r'^myview/', MyView.as_view()),
]

console

http GET http://127.0.0.1:8000/myview/
HTTP/1.0 401 Unauthorized

http OPTIONS http://127.0.0.1:8000/myview/
HTTP/1.0 401 Unauthorized

Comportement attendu

http GET http://127.0.0.1:8000/myview/
HTTP/1.0 401 Unauthorized

http OPTIONS http://127.0.0.1:8000/myview/
HTTP/1.0 200 OK

Contournement temporaire

class IsAuthenticated(permissions.IsAuthenticated):

    def has_permission(self, request, view):
        if request.method == 'OPTIONS':
            return True
        return super(IsAuthenticated, self).has_permission(request, view)

Commentaire le plus utile

Version DRF : 3.7.3
Python 3.6.3

La solution de contournement temporaire mentionnée ci-dessus n'a pas fonctionné pour moi. Toutes les demandes étaient authentifiées, qu'elles soient PUT, POST, GET, OPTIONS, etc.

Cela a fonctionné pour le changer en :

# myapp/permissions.py
from rest_framework.permissions import IsAuthenticated

class AllowOptionsAuthentication(IsAuthenticated):
    def has_permission(self, request, view):
        if request.method == 'OPTIONS':
            return True
        return request.user and request.user.is_authenticated

Et dans settings.py :

REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': ( 'rest_framework.authentication.TokenAuthentication',),
    'DEFAULT_PERMISSION_CLASSES': (
        'myapp.permissions.AllowOptionsAuthentication',
    )
}

Tous les 22 commentaires

Version DRF : 3.7.3
Python 3.6.3

La solution de contournement temporaire mentionnée ci-dessus n'a pas fonctionné pour moi. Toutes les demandes étaient authentifiées, qu'elles soient PUT, POST, GET, OPTIONS, etc.

Cela a fonctionné pour le changer en :

# myapp/permissions.py
from rest_framework.permissions import IsAuthenticated

class AllowOptionsAuthentication(IsAuthenticated):
    def has_permission(self, request, view):
        if request.method == 'OPTIONS':
            return True
        return request.user and request.user.is_authenticated

Et dans settings.py :

REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': ( 'rest_framework.authentication.TokenAuthentication',),
    'DEFAULT_PERMISSION_CLASSES': (
        'myapp.permissions.AllowOptionsAuthentication',
    )
}

J'ai également rencontré ce même problème. Je suis d'accord avec la solution proposée et merci pour la solution de contournement temporaire !

Pas convaincu à 100% que "devrait autoriser toutes les requêtes OPTIONS par défaut" est exactement le comportement que nous souhaitons car de nombreux développeurs utilisant le framework REST utilisent les requêtes OPTIONS pour des cas autres que les requêtes de contrôle en amont CORS.

Cependant, je pense que nous voulons probablement avoir une option CORS bénie . Je ne sais pas si cela signifie que nous devrions inclure https://github.com/ottoyiu/django-cors-headers/ directement, ou si nous devrions le référencer plus fortement.

Ce problème se résout-il une fois que vous utilisez ce package ?

Ce package n'est pas vraiment la solution, il ajoute simplement des en-têtes CORS mais ne corrige pas le fait que OPTIONS renvoie HTTP401 si l'autorisation DRF n'est pas spécifiquement accordée pour toutes les demandes non authentifiées.

OMI, il devrait être introduit dans l'autre sens, c'est-à-dire autorisé par défaut avec un avis de modification avec rupture.
C'est le comportement attendu selon les spécifications du W3C, et mentionner un tiers dans la doc pour corriger une mauvaise implémentation n'est pas vraiment une solution propre...

Ainsi, bien qu'il y ait des gens qui utilisent OPTIONS pour des choses non liées à CORS (je suis l'un d'entre eux), il y a potentiellement beaucoup plus de choses qui s'attendent implicitement à ce que les choses fonctionnent hors de la boîte, puisque les mécanismes CORS sont appliqués par de plus en plus de navigateurs. En fait, ces mécanismes génèrent potentiellement plus de 99% des requêtes OPTIONS sur le Web.

Conflit intéressant. Je comprends totalement le problème d'avoir une autorisation implicite par défaut sur la méthode OPTIONS. Je suis naturellement plus orienté vers une solution plus sécurisée et je suppose que cela dépend de l'objectif du développeur pour DRF. Un refus par défaut me semble donc plus approprié.

Il est également possible que la spécification W3C permettant d'autoriser les OPTIONS ne s'applique que dans le contexte de CORS. De toute évidence, DRF n'a aucun moyen de connaître le contexte et devrait être un paramètre fourni au développeur.

Si CORS est prévu, autorisez toutes les requêtes OPTIONS. CORS étant involontaire par défaut.

En fait, je pense que nous pourrions envisager un changement de rupture pour une version majeure, le comportement actuel restant disponible. Notre comportement OPTIONS existant est en place depuis que JSONP était ce que les gens utiliseraient plutôt que CORS, nous devons donc probablement une actualisation qui supprime l'ensemble assez ad hoc d'informations par défaut que nous exposons et se concentre uniquement sur OPTIONS pour CORS par défaut .

+1

@tomchristie avez-vous une idée du moment où une mise à jour/correction de version majeure se produirait ? Je suis également touché par ce problème.

La solution de contournement de @medakk est parfaite, en attendant !

Jalonner cela pour s'assurer qu'il soit correctement pris en compte pour 3.9

Une autre solution de contournement consiste à gérer cela explicitement dans la vue. Dans le cas où vous avez plusieurs permission_classes, c'est plus DRY, car chacune d'entre elles aurait besoin de cette gestion.

def check_permissions(self, request):
        if request.method == 'OPTIONS':
            return
        super(MyApiView, self).check_permissions(request)

Pour en revenir plus loin, le package https://github.com/OttoYiu/django-cors-headers gère les demandes OPTIONS en amont

Pas évident pour moi que nous ayons un problème ici.

Les réponses de contrôle en amont ne touchent pas au cadre REST : https://github.com/ottoyiu/django-cors-headers/blob/master/corsheaders/middleware.py#L83

Merci. Nous n'utilisons pas django-cors-headers, mais nous devrions peut-être l'être.
Je suis toujours d'accord avec les commentaires précédents selon lesquels DRF ne devrait pas avoir besoin d'un package externe pour gérer les demandes CORS conformes au W3C.

Oui, il existe de nombreux packages tiers pour contourner le problème.
Mais encore une fois, ce ticket a été créé car DRF est incompatible avec les spécifications W3C CORS, et il devrait être corrigé.

De nos jours, la grande majorité des demandes de OPTIONS effectuées aux points de terminaison sont à des fins de contrôle en amont, et il est probablement trompeur pour les nouveaux utilisateurs de DRF de devoir gérer ces problèmes manuellement en raison de choix avisés de DRF.

De nos jours, la grande majorité des demandes de OPTIONS effectuées aux points de terminaison sont à des fins de contrôle en amont, et il est probablement trompeur pour les nouveaux utilisateurs de DRF de devoir gérer ces problèmes manuellement en raison de choix avisés de DRF.

Ce n'est pas parce que CORS a détourné la méthode OPTIONS que sa sécurité doit être modifiée par défaut pour s'adapter. Surtout si le support CORS est correctement mis en œuvre, il fonctionnera comme il le faut. Désolé, mais je pense que les développeurs qui souhaitent que cela soit modifié doivent vérifier leurs propres opinions et non les mainteneurs de DRF.

Peut-être une amélioration de la documentation pour suggérer que si CORS est nécessaire, qu'un module pour aider à la bonne mise en œuvre soit utilisé au lieu de réinventer la roue.

Baissons le niveau.

Le middleware est l'endroit idéal pour traiter les en-têtes CORS. Nous pourrions intégrer cela dans le cadre REST, mais le package existant le couvre déjà très bien.

Le corps que le framework REST renvoie pour les requêtes OPTIONS n'est pas pertinent ici, car les requêtes CORS contrôlées en amont doivent de toute façon être interceptées par le middleware, et les requêtes CORS standard peuvent être de n'importe quelle méthode HTTP.

Je serais parfaitement heureux que le framework REST passe à ne pas servir ces corps de réponse par défaut, mais c'est légèrement différent de la question de la prise en charge de CORS, et ce n'est pas tant que le framework REST a un comportement d'opinion là-bas, mais plutôt qu'il a comportement antérieur à la mise en œuvre généralisée de CORS.

Il me semble que la solution middleware à elle seule est insuffisante ou du moins nécessite une certaine coopération de la part de DRF. Si vous regardez le code référencé, vous pouvez voir que les valeurs par défaut globales sont utilisées pour fournir les en-têtes CORS. Mais comment savoir globalement quels en-têtes chaque vue prend en charge ?

En tant que tel, j'aimerais voir un moyen standard pour le middleware de se connecter à ces options de configuration à partir de vues/vues : une méthode allowed_cors_headers par exemple.

Je suis d'accord que cela n'a pas besoin d'être géré par les vues, mais cela doit absolument respecter la connaissance qu'a la vue de ce qu'elle peut servir.

Il peut également être intéressant d'avoir un remplacement au niveau du routeur à appliquer à toutes les vues du routeur.

Considérations supplémentaires:

  • variable selon l'origine
  • variant en fonction du type de contenu

Ce n'est pas mon choix préféré, mais la tendance est à l'utilisation d'un tiers.

Je pense que nous pourrons fermer ce ticket une fois que la documentation aura été mise à jour avec les informations appropriées pour les utilisateurs de CORS (telles que : "Attention, DRF ne remplit pas les spécifications W3C CORS sur les requêtes OPTIONS. Si vous utilisez OPTIONS pour CORS, n'oubliez pas d'ajouter un middleware approprié, sinon vos demandes de contrôle en amont pourraient échouer en raison d'un manque d'autorisation", etc.)

La documentation existante https://www.django-rest-framework.org/topics/ajax-csrf-cors/#cors est parfaitement raisonnable, et traiter avec CORS dans le middleware est la bonne approche dans tous les cas.

Mais comment savoir globalement quels en-têtes chaque vue prend en charge ?

Vous n'avez pas besoin de savoir quelle est la politique CORS du site.

Heureux d'accepter une demande d'extraction rendant les documents CORS plus importants ou de les inclure ailleurs, ce qui est également approprié. En dehors de cela, il n'y a pas de problème pouvant donner lieu à une action ici.

Mon incompréhension de la spécification, oups.

Dans ce cas, le support serait mieux inclus dans la contribution des sites django
paquet, pas drf.

Le mar. 19 févr. 2019 10h22, Tom Christie [email protected] a
écrit :

La documentation existante
https://www.django-rest-framework.org/topics/ajax-csrf-cors/#cors est
parfaitement raisonnable, et traiter avec CORS dans le middleware est le bon
approche en tout cas.

Mais comment savoir globalement quels en-têtes chaque vue prend en charge ?

Vous n'avez pas besoin de savoir quelle est la politique CORS du site.

-
Vous recevez ceci parce que vous avez commenté.
Répondez directement à cet e-mail, consultez-le sur GitHub
https://github.com/encode/django-rest-framework/issues/5616#issuecomment-465146969 ,
ou couper le fil
https://github.com/notifications/unsubscribe-auth/AFhtlM6bG-Bs2CvoO972pIfwvxLHbzAxks5vPAjAgaJpZM4Qlvkn
.

Cette page vous a été utile?
0 / 5 - 0 notes