Django-rest-framework: DRF debe autorizar todas las solicitudes de OPCIONES de forma predeterminada

Creado en 21 nov. 2017  ·  22Comentarios  ·  Fuente: encode/django-rest-framework

Después de la discusión sobre el n. ° 908

Los permisos para solicitudes de OPTIONS (metadatos) se manejan incorrectamente en DRF.

Según las especificaciones del OPTIONS verificación previa NO están autenticadas, lo que significa que los usuarios siempre obtendrán un error 401 al realizar una verificación previa de una solicitud de puntos finales autenticados, porque los navegadores modernos nunca envían esto según las especificaciones:

De lo contrario, realice una solicitud de verificación previa. Obtenga la URL de la solicitud desde el origen de la fuente de origen utilizando la fuente de referencia como fuente de referencia anulada con la marca de redirección manual y la marca de bloqueo de cookies configurada, utilizando el método OPTIONS , y con las siguientes restricciones adicionales:

  • Incluya un encabezado Access-Control-Request-Method con como valor de campo de encabezado el método de solicitud (incluso cuando sea un método simple).
  • Si los encabezados de solicitud del autor no están vacíos, incluya un encabezado Access-Control-Request-Headers con como valor de campo de encabezado una lista separada por comas de los nombres de los campos de encabezado de los encabezados de solicitud del autor en orden lexicográfico, cada uno convertido a minúsculas ASCII (incluso cuando uno o más son un simple encabezado).
  • Excluya los encabezados de solicitud de autor.
  • ➡️ Excluya las credenciales de usuario .
  • Excluya el cuerpo de la entidad de solicitud.

Creo que este debería ser el comportamiento predeterminado aquí.
DRF debe autorizar todas las solicitudes OPTIONS de forma predeterminada para las clases de permisos estándar (IsAuthenticated, IsAdminUser, etc.) y los usuarios pueden anular esto cuando explícitamente necesitan proteger la información de metadatos (infringiendo la compatibilidad estándar de CORS)

Pasos para reproducir :

views.py

class MyView(APIView):

    permission_classes = (IsAuthenticated,)

urls.py

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

consola

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

Comportamiento esperado

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

Workaroud temporal

class IsAuthenticated(permissions.IsAuthenticated):

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

Comentario más útil

Versión de DRF: 3.7.3
Python 3.6.3

La solución temporal mencionada anteriormente no funcionó para mí. Todas las solicitudes se estaban autenticando independientemente de si eran PUT, POST, GET, OPTIONS, etc.

Funcionó para cambiarlo a:

# 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

Y en settings.py:

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

Todos 22 comentarios

Versión de DRF: 3.7.3
Python 3.6.3

La solución temporal mencionada anteriormente no funcionó para mí. Todas las solicitudes se estaban autenticando independientemente de si eran PUT, POST, GET, OPTIONS, etc.

Funcionó para cambiarlo a:

# 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

Y en settings.py:

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

También me he encontrado con el mismo problema. Estoy de acuerdo con la solución propuesta y gracias por la solución temporal.

No estoy 100% convencido de que "debería autorizar todas las solicitudes de OPCIONES de forma predeterminada" es exactamente el comportamiento que queremos, ya que hay muchos desarrolladores que usan el marco REST que usan las solicitudes OPTIONS para casos distintos a las solicitudes de verificación previa de CORS.

Sin embargo, creo que probablemente queremos tener una opción CORS bendecida . No sé si eso significa que deberíamos incluir https://github.com/ottoyiu/django-cors-headers/ directamente, o si deberíamos hacer referencia a él con más detalle.

¿Este problema se resuelve una vez que se usa ese paquete?

Este paquete no es realmente la solución, solo agrega encabezados CORS pero no corrige el hecho de que OPTIONS devuelve HTTP401 si el permiso DRF no se otorga específicamente para todas las solicitudes no autenticadas.

En mi opinión, debería introducirse al revés, es decir, permitido por defecto con un aviso de cambio de última hora.
Este es el comportamiento esperado según las especificaciones de W3C, y mencionar a un tercero en el documento para parchear una implementación incorrecta no es realmente una solución limpia ...

Entonces, aunque hay personas que usan OPTIONS para cosas no relacionadas con CORS (yo soy uno de ellos), hay muchas más personas que esperan implícitamente que las cosas funcionen de forma inmediata, ya que los mecanismos de CORS están siendo aplicados por más y más navegadores. De hecho, estos mecanismos generan potencialmente más del 99% de las solicitudes de OPCIONES a través de la web.

Interesante conflicto. Entiendo totalmente el problema de tener un permiso implícito de forma predeterminada en el método OPTIONS. Naturalmente, estoy más inclinado hacia una solución más segura y supongo que depende del propósito del desarrollador para DRF. Entonces, una denegación predeterminada me parece más apropiada.

También es posible que la especificación W3C para permitir OPCIONES solo se aplique en el contexto de CORS. Obviamente, DRF no tiene forma de conocer el contexto y debería ser una configuración proporcionada al desarrollador.

Si está destinado a CORS, permita todas las solicitudes de OPCIONES. CORS no está intencionado de forma predeterminada.

En realidad, creo que podríamos considerar un cambio importante para una versión principal, con el comportamiento actual disponible. Nuestro comportamiento de OPCIONES existente ha estado en su lugar desde que JSONP era lo que la gente usaría en lugar de CORS, por lo que probablemente se deba a una actualización que elimine el conjunto bastante ad-hoc de información predeterminada que exponemos y, en su lugar, se concentra únicamente en OPCIONES para CORS de forma predeterminada. .

+1

@tomchristie , ¿tiene alguna idea de cuándo se produciría una actualización / corrección de una versión principal? También me estoy ocupando de este problema.

¡La solución de @medakk es perfecta, mientras tanto!

Milestoning esto para asegurarse de que reciba la consideración adecuada para 3.9

Una solución alternativa es manejar esto explícitamente en la vista. En el caso de que tenga varias clases de permisos, esto es más DRY, ya que cada una de ellas necesitaría este manejo.

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

Volviendo a esto, habiéndolo investigado más a fondo, el paquete https://github.com/OttoYiu/django-cors-headers maneja las solicitudes OPTIONS prefiguradas en middleware y devuelve una respuesta directamente. No importa lo que haga el marco REST aquí porque la solicitud / respuesta se intercepta antes de que llegue al marco REST.

No es obvio que tenemos un problema aquí.

Las respuestas de verificación previa no tocan el marco REST: https://github.com/ottoyiu/django-cors-headers/blob/master/corsheaders/middleware.py#L83

Gracias. No estamos usando django-cors-headers, pero tal vez deberíamos hacerlo.
Todavía estoy de acuerdo con los comentarios anteriores de que DRF no debería necesitar un paquete externo para manejar las solicitudes CORS compatibles con W3C.

Sí, hay muchas formas / paquetes de terceros para evitar el problema.
Pero nuevamente, este ticket se creó porque DRF es incompatible con las especificaciones W3C CORS, y debería corregirse.

Hoy en día, la gran mayoría de las solicitudes OPTIONS realizadas a los puntos finales son para fines de verificación previa, y probablemente sea engañoso para los nuevos usuarios de DRF tener que manejar estos problemas manualmente debido a las opciones opuestas de DRF.

Hoy en día, la gran mayoría de las solicitudes OPTIONS realizadas a los puntos finales son para fines de verificación previa, y probablemente sea engañoso para los nuevos usuarios de DRF tener que manejar estos problemas manualmente debido a las opciones opuestas de DRF.

El hecho de que CORS haya secuestrado el método OPTIONS no significa que su seguridad deba modificarse de forma predeterminada para adaptarse. Especialmente si la compatibilidad con CORS se implementa correctamente, funcionará de la forma necesaria. Lo siento, pero creo que los desarrolladores que quieran cambiar esto deben verificar sus propias opiniones y no los mantenedores de DRF.

Quizás una mejora de la documentación para sugerir que si se necesita CORS, se use un módulo para ayudar en la implementación adecuada en lugar de reinventar la rueda.

Bajemos el nivel.

El middleware es el lugar adecuado para tratar los encabezados CORS. Podríamos construir ese marco en reposo, pero el paquete existente que ya tiene cubierto muy bien.

El cuerpo que el marco REST devuelve para las solicitudes OPTIONS es irrelevante aquí, ya que las solicitudes CORS prefluidas deben ser interceptadas por el middleware de todos modos, y las solicitudes CORS estándar pueden ser de cualquier método HTTP.

Estaría perfectamente feliz de que el marco REST pasara a no ofrecer esos cuerpos de respuesta de forma predeterminada, pero eso es ligeramente diferente al problema del soporte de CORS, y no es tanto que el marco REST tenga un comportamiento obstinado allí, sino que lo ha hecho. comportamiento anterior a CORS que se está implementando ampliamente.

Me parece que la solución de middleware por sí sola es insuficiente o al menos requiere cierta cooperación de DRF. Si observa el código al que se hace referencia, puede ver que se están utilizando valores predeterminados globales para proporcionar los encabezados CORS. Pero, ¿cómo puedo saber globalmente qué encabezados admite cada vista?

Como tal, me gustaría ver una forma estándar para que el middleware se conecte a estas opciones de configuración desde conjuntos de vistas / vistas: un método allowed_cors_headers , por ejemplo.

Estoy de acuerdo en que esto no necesita ser manejado por vistas, pero es absolutamente necesario respetar el conocimiento de la vista de lo que puede servir.

También podría valer la pena tener una anulación a nivel de enrutador para aplicar a todas las vistas en el enrutador.

Consideraciones adicionales:

  • variando según el origen
  • variando según el tipo de contenido

Esta no es mi elección favorita, pero la tendencia va hacia el uso de un tercero.

Creo que podemos cerrar este ticket una vez que la documentación se haya actualizado con la información adecuada para los usuarios de CORS (como: "Cuidado, DRF no cumple con las especificaciones de W3C CORS en las solicitudes de OPTIONS. Si usa OPTIONS para CORS, no olvide agregar un middleware adecuado, de lo contrario, sus solicitudes de verificación previa podrían fallar debido a la falta de autorización ", etc.)

La documentación existente https://www.django-rest-framework.org/topics/ajax-csrf-cors/#cors es perfectamente razonable, y tratar con CORS en middleware es el enfoque correcto en cualquier caso.

Pero, ¿cómo puedo saber globalmente qué encabezados admite cada vista?

No es necesario, necesita saber cuál es la política CORS del sitio.

Me complace aceptar una solicitud de extracción que hace que los documentos CORS sean más destacados o que los incluya en otro lugar que también sea apropiado. Aparte de eso, no hay ningún problema procesable aquí.

Mi malentendido de la especificación, vaya.

En ese caso, el soporte estaría mejor incluido en la contribución de los sitios de django.
paquete, no drf.

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

La documentación existente
https://www.django-rest-framework.org/topics/ajax-csrf-cors/#cors es
perfectamente razonable, y tratar con CORS en middleware es la correcta
enfoque en cualquier caso.

Pero, ¿cómo puedo saber globalmente qué encabezados admite cada vista?

No es necesario, necesita saber cuál es la política CORS del sitio.

-
Estás recibiendo esto porque comentaste.
Responda a este correo electrónico directamente, véalo en GitHub
https://github.com/encode/django-rest-framework/issues/5616#issuecomment-465146969 ,
o silenciar el hilo
https://github.com/notifications/unsubscribe-auth/AFhtlM6bG-Bs2CvoO972pIfwvxLHbzAxks5vPAjAgaJpZM4Qlvkn
.

¿Fue útil esta página
0 / 5 - 0 calificaciones