Django-rest-framework: DRF deve autorizar todas as solicitações de OPÇÕES por padrão

Criado em 21 nov. 2017  ·  22Comentários  ·  Fonte: encode/django-rest-framework

Seguindo a discussão em # 908

As permissões para solicitações OPTIONS (metadados) são tratadas incorretamente no DRF.

De acordo com as especificações do OPTIONS NÃO são autenticadas, o que significa que os usuários sempre receberão um erro 401 ao comprovar uma solicitação de endpoints autenticados, porque os navegadores modernos nunca enviam isso de acordo com as especificações:

Caso contrário, faça uma solicitação de comprovação. Busque o URL da solicitação da origem da origem usando a fonte do referenciador como fonte do referenciador de substituição com o sinalizador de redirecionamento manual e o sinalizador de cookies de bloqueio definido, usando o método OPTIONS e com as seguintes restrições adicionais:

  • Inclua um cabeçalho Access-Control-Request-Method com o valor do campo de cabeçalho do método de solicitação (mesmo quando for um método simples).
  • Se os cabeçalhos de solicitação do autor não estiverem vazios, inclua um cabeçalho Access-Control-Request-Headers com o valor do campo de cabeçalho uma lista separada por vírgulas dos nomes dos campos de cabeçalho dos cabeçalhos de solicitação do autor em ordem lexicográfica, cada um convertido para ASCII minúsculo (mesmo quando um ou mais são um cabeçalho simples).
  • Exclua os cabeçalhos de solicitação do autor.
  • ➡️ Exclua as credenciais do usuário .
  • Exclua o corpo da entidade de solicitação.

Acho que este deve ser o comportamento padrão aqui.
O DRF deve autorizar todas as solicitações OPTIONS por padrão para classes de permissão padrão (IsAuthenticated, IsAdminUser, etc) e os usuários podem substituir isso quando precisarem explicitamente proteger as informações de metadados (infringindo a compatibilidade CORS padrão)

Passos para reproduzir :

views.py

class MyView(APIView):

    permission_classes = (IsAuthenticated,)

urls.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

Comportamento 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 temporária

class IsAuthenticated(permissions.IsAuthenticated):

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

Comentários muito úteis

Versão DRF: 3.7.3
Python 3.6.3

A solução temporária mencionada acima não funcionou para mim. Todas as solicitações estavam sendo autenticadas independentemente de serem PUT, POST, GET, OPTIONS, etc.

Funcionou mudando para:

# 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

E em settings.py:

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

Todos 22 comentários

Versão DRF: 3.7.3
Python 3.6.3

A solução temporária mencionada acima não funcionou para mim. Todas as solicitações estavam sendo autenticadas independentemente de serem PUT, POST, GET, OPTIONS, etc.

Funcionou mudando para:

# 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

E em settings.py:

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

Eu também enfrentei o mesmo problema. Eu concordo com a solução proposta e obrigado pela solução temporária!

Não estar 100% convencido de que "deve autorizar todas as solicitações OPTIONS por padrão" é exatamente o comportamento que desejamos, pois há muitos desenvolvedores usando a estrutura REST que usam as solicitações OPTIONS para casos diferentes de solicitações de comprovação CORS.

No entanto, acho que provavelmente queremos uma opção de CORS abençoada . Não sei se isso significa que devemos incluir https://github.com/ottoyiu/django-cors-headers/ diretamente, ou se devemos referenciá-lo com mais atenção.

Esse problema é resolvido depois de usar esse pacote?

Este pacote não é realmente a solução, ele apenas anexa cabeçalhos CORS, mas não corrige o fato de que OPTIONS retorna HTTP401 se a permissão DRF não for concedida especificamente para todas as solicitações não autenticadas.

OMI deve ser introduzido ao contrário, ou seja, permitido por padrão com um aviso de alteração de última hora.
Este é o comportamento esperado de acordo com as especificações do W3C, e mencionar um terceiro no doc para corrigir uma implementação errada não é realmente uma solução limpa ...

Portanto, embora haja pessoas usando OPÇÕES para coisas não relacionadas ao CORS (eu sou uma delas), existem potencialmente muito mais implicitamente esperando que as coisas funcionem fora da caixa, uma vez que os mecanismos CORS estão sendo impostos por mais e mais navegadores. Na verdade, esses mecanismos geram potencialmente mais de 99% das solicitações de OPÇÕES na web.

Conflito interessante. Eu entendo totalmente o problema de ter uma permissão implícita por padrão no método OPTIONS. Estou naturalmente mais inclinado para uma solução mais segura e suponho que depende do propósito do desenvolvedor para DRF. Portanto, uma negação padrão parece mais apropriada para mim.

Também é possível que a especificação W3C para permitir OPÇÕES só se aplique no contexto do CORS. Obviamente, o DRF não tem como saber o contexto e deve ser uma configuração fornecida ao dev.

Se o CORS for pretendido, permita todas as solicitações de OPÇÕES. CORS sendo não intencional por padrão.

Na verdade, acho que podemos considerar uma alteração significativa para uma versão principal, com o comportamento atual permanecendo disponível. Nosso comportamento OPTIONS existente está em vigor desde que JSONP era o que as pessoas usariam em vez de CORS, então provavelmente devemos uma atualização que remove o conjunto bastante ad-hoc de informações padrão que expomos e, em vez disso, se concentra apenas em OPÇÕES para CORS por padrão .

+1

@tomchristie , você tem alguma ideia de quando uma atualização / correção de versão principal aconteceria? Eu também estou acertando esse problema.

A solução alternativa de @medakk é perfeita, entretanto!

Milestar isso para garantir que receba a devida consideração para 3,9

Uma solução alternativa é lidar com isso explicitamente na visualização. No caso de você ter várias permission_classes, isso é mais DRY, pois cada uma delas precisaria desse tratamento.

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

Voltando a isso depois de examinar mais a fundo - o pacote https://github.com/OttoYiu/django-cors-headers lida com solicitações OPTIONS pré-iluminadas no middleware e retorna uma resposta direta. Não importa o que a estrutura REST faz aqui, porque a solicitação / resposta é interceptada antes de atingir a estrutura REST.

Não me óbvio que nós temos um problema aqui.

Respostas de comprovação não afetam a estrutura REST: https://github.com/ottoyiu/django-cors-headers/blob/master/corsheaders/middleware.py#L83

Obrigado. Não estamos usando django-cors-headers, mas talvez devêssemos usar.
Ainda concordo com os comentários anteriores de que o DRF não deve precisar de um pacote externo para lidar com solicitações CORS compatíveis com W3C.

Sim, existem vários pacotes de meios / terceiros para contornar o problema.
Mas, novamente, esse tíquete foi criado porque o DRF é incompatível com as especificações W3C CORS e deve ser corrigido.

Hoje em dia, a grande maioria das solicitações de OPTIONS executadas para endpoints são para fins de comprovação e provavelmente é enganoso para novos usuários de DRF ter que lidar com esses problemas manualmente por causa de escolhas opinativas de DRF.

Hoje em dia, a grande maioria das solicitações de OPTIONS executadas para endpoints são para fins de comprovação e provavelmente é enganoso para novos usuários de DRF ter que lidar com esses problemas manualmente por causa de escolhas opinativas de DRF.

Só porque o CORS sequestrou o método OPTIONS não significa que sua segurança deva ser alterada por padrão para acomodar. Especialmente se o suporte CORS for implementado corretamente, ele funcionará da maneira que precisa. Desculpe, mas acho que os desenvolvedores que desejam que isso seja alterado precisam verificar suas próprias opiniões e não os mantenedores do DRF.

Talvez um aprimoramento da documentação para sugerir que, se o CORS for necessário, um módulo para auxiliar na implementação adequada seja usado em vez de reinventar a roda.

Vamos baixar o nível.

Middleware é o lugar sensato para lidar com cabeçalhos CORS. Poderíamos construir isso na estrutura REST, mas o pacote existente já o tem coberto muito bem.

O corpo que a estrutura REST retorna para solicitações OPTIONS é irrelevante aqui, já que as solicitações CORS pré-iluminadas devem ser interceptadas pelo middleware de qualquer maneira, e as solicitações CORS padrão podem ser de qualquer método HTTP.

Eu seria perfeitamente feliz para framework REST para mover a não servir-se aqueles corpos de resposta por padrão, mas isso é um pouco diferente para a questão do apoio CORS, e não é tanto que quadro RESTO tem opinativo comportamento lá, mas sim que ele tem comportamento anterior ao CORS sendo amplamente implementado.

Parece-me que a solução de middleware por si só é insuficiente ou pelo menos requer alguma cooperação do DRF. Se você olhar o código referenciado, verá que os padrões globais estão sendo usados ​​para fornecer os cabeçalhos CORS. Mas como posso saber globalmente quais cabeçalhos cada visualização suporta?

Como tal, gostaria de ver uma maneira padrão para o middleware se conectar a essas opções de configuração de viewsets / views: um método allowed_cors_headers por exemplo.

Concordo que isso não precisa ser tratado por visualizações, mas é absolutamente necessário respeitar o conhecimento da visualização sobre o que ela pode servir.

Também pode valer a pena ter uma substituição no nível do roteador para aplicar a todas as visualizações no roteador.

Considerações adicionais:

  • variando com base na origem
  • variando com base no tipo de conteúdo

Esta não é minha escolha favorita, mas a tendência é usar um terceiro.

Acho que podemos fechar este tíquete assim que a documentação for atualizada com as informações adequadas para os usuários do CORS (como: "Cuidado, o DRF não atende às especificações W3C CORS nas solicitações de OPÇÕES. Se você usar OPÇÕES para CORS, não se esqueça de adicionar um middleware adequado, caso contrário, suas solicitações de comprovação podem falhar devido à falta de autorização "etc)

A documentação existente https://www.django-rest-framework.org/topics/ajax-csrf-cors/#cors é perfeitamente razoável e lidar com CORS em middleware é a abordagem correta em qualquer caso.

Mas como posso saber globalmente quais cabeçalhos cada visualização suporta?

Você não precisa - você precisa saber qual é a política CORS do site.

Fico feliz em aceitar uma solicitação de pull tornando os documentos CORS mais proeminentes ou incluindo-os em outro lugar apropriado. Fora isso, não há nenhum problema acionável aqui.

Meu mal-entendido sobre as especificações, opa.

Nesse caso, o suporte seria melhor incluído na contribuição dos sites django
pacote, não drf.

Le mar. 19 de fevereiro. 2019 10:22, Tom Christie [email protected] a
écrit:

A documentação existente
https://www.django-rest-framework.org/topics/ajax-csrf-cors/#cors is
perfeitamente razoável, e lidar com CORS em middleware é o correto
abordagem em qualquer caso.

Mas como posso saber globalmente quais cabeçalhos cada visualização suporta?

Você não precisa - você precisa saber qual é a política CORS do site.

-
Você está recebendo isto porque comentou.
Responda a este e-mail diretamente, visualize-o no GitHub
https://github.com/encode/django-rest-framework/issues/5616#issuecomment-465146969 ,
ou silenciar o tópico
https://github.com/notifications/unsubscribe-auth/AFhtlM6bG-Bs2CvoO972pIfwvxLHbzAxks5vPAjAgaJpZM4Qlvkn
.

Esta página foi útil?
0 / 5 - 0 avaliações