Requests: O cabeçalho de autorização da sessão não é enviado no redirecionamento

Criado em 28 dez. 2015  ·  35Comentários  ·  Fonte: psf/requests

Estou usando solicitações para acessar developer-api.nest.com e definindo um cabeçalho de autorização com um token de portador. Em algumas solicitações, essa API responde com um redirecionamento 307. Quando isso acontecer, ainda preciso que o cabeçalho de autorização seja enviado na solicitação subsequente. Tentei usar requests.get() e também uma sessão.

Suponho que eu poderia contornar isso não permitindo redirecionamentos, detectando o 307 e emitindo a nova solicitação por conta própria, mas estou me perguntando se isso é um bug. Devo esperar que o cabeçalho de autorização seja enviado em todas as solicitações feitas no contexto de uma sessão?

In [41]: s = requests.Session()

In [42]: s.headers
Out[42]: {'Accept': '*/*', 'Accept-Encoding': 'gzip, deflate', 'Connection': 'keep-alive', 'User-Agent': 'python-requests/2.7.0 CPython/3.4.3 Darwin/15.2.0'}

In [43]: s.headers['Authorization'] = "Bearer <snip>"

In [45]: s.get("https://developer-api.nest.com/devices/thermostats/")
Out[45]: <Response [401]>

In [46]: s.get("https://developer-api.nest.com/devices/thermostats/")
Out[46]: <Response [200]>

In [49]: Out[45].history
Out[49]: [<Response [307]>]

In [50]: Out[46].history
Out[50]: []

In [51]: Out[45].request.headers
Out[51]: {'Accept': '*/*', 'Accept-Encoding': 'gzip, deflate', 'Connection': 'keep-alive', 'User-Agent': 'python-requests/2.7.0 CPython/3.4.3 Darwin/15.2.0'}

In [52]: Out[46].request.headers
Out[52]: {'Accept': '*/*', 'Accept-Encoding': 'gzip, deflate', 'Connection': 'keep-alive', 'User-Agent': 'python-requests/2.7.0 CPython/3.4.3 Darwin/15.2.0', 'Authorization': 'Bearer <snip>'}
Bug

Comentários muito úteis

Existem duas soluções alternativas específicas do Nest.

Uma é passar o parâmetro auth com o access_token em vez de usar o cabeçalho Authorization. Eu encontrei isso em https://gist.github.com/tylerdave/409ffa08e1d47b1a1e23

Outra é salvar um dicionário com os cabeçalhos que você usaria, não siga os redirecionamentos e, em seguida, faça uma segunda solicitação passando os cabeçalhos novamente:

    headers = {'Authorization': 'Bearer ' + access_token, 'Content-Type': 'application/json'}
    initial_response = requests.get('https://developer-api.nest.com', headers=headers, allow_redirects=False)
    if initial_response.status_code == 307:
        api_response = requests.get(initial_response.headers['Location'], headers=headers, allow_redirects=False)

Todos 35 comentários

Para onde está o redirecionamento?

Ah, um domínio diferente. firebase-apiserver03-tah01-iad01.dapi.production.nest.com

Sim, isso é um tanto deliberado: somos muito agressivos ao remover cabeçalhos de autorização quando redirecionados para um novo host. Este é um recurso de segurança para lidar com o CVE 2014-1829 , que foi causado pela persistência de cabeçalhos em redirecionamentos fora do host.

No entanto, de uma certa perspectiva, ainda temos um bug aqui, porque você definiu o cabeçalho Authorization em Session , não na solicitação. Em princípio, isso significa "Não me importa para onde vai o redirecionamento, adicione o cabeçalho". Ainda _ acho_ que prefiro essa abordagem, que pelo menos garante que não estejamos abertos a qualquer forma de ataque, mesmo que torne essa instância específica um pouco mais complicada. No entanto, estou aberto para ser convencido de que estamos sendo muito paranóicos aqui.

No entanto, estou aberto para ser convencido de que estamos sendo muito paranóicos aqui.

Estou menos aberto a ser convencido, mas disposto a ouvir.

Dito isso, um mecanismo de Auth separado poderia ser escrito para persistir esses cabeçalhos em domínios _publicados_, o que exigiria que fizéssemos algum trabalho em rebuild_auth .

Não vou discutir sobre a segurança de como funciona agora. No entanto, seria bom ter algum mecanismo para optar pelo comportamento "inseguro". Talvez definindo um domínio base para persistir esses cabeçalhos (nest.com, neste caso) ou talvez uma lista de domínios que podem ser enviados.

Sim, é muito mais complexidade do que o núcleo de solicitações jamais fornecerá. É por isso que estou me perguntando se uma classe / manipulador de Auth separado pode funcionar melhor para esse tipo de coisa. Não estou convencido de que funcionará porque estou bastante certo de que não chamamos prepare_auth incondicionalmente.

Não funcionará no modelo padrão porque não chamamos incondicionalmente prepare_auth . No entanto, um adaptador de transporte pode ser usado para cumprir essa função, mesmo se for um uso um pouco incomum dessa API.

Eu acho que um TA é absolutamente a coisa errada a se recomendar aqui.

  • Se auth for fornecido a uma sessão, ele deve ser enviado para cada solicitação que a sessão fizer.
  • Talvez devêssemos remover session.auth . Não é particularmente útil.

Se auth for fornecido a uma sessão, ele deve ser enviado para cada solicitação que a sessão fizer.

Eu discordo fundamentalmente. As sessões não são usadas para um único domínio; se fossem, eu não teria nenhum problema com isso.

Talvez devêssemos remover session.auth. Não é particularmente útil.

Eu acho que é útil. Eu acho que seria melhor se atribuir uma tupla a ele não fosse permitido. Prefiro ver uma classe Auth que especifica em quais domínios usá-lo. Poderíamos apenas adotar o AuthHandler de solicitações-toolbelt, que permite às pessoas especificar credenciais para um domínio ao usar solicitações. Isso fornece uma maneira um pouco mais segura de lidar com a autenticação baseada em sessão. A desvantagem é que ele exige que os usuários optem por esse tipo de autenticação.

Eu também preciso que isso seja corrigido para que eu possa fazer os cabeçalhos persistirem para redirecionamentos.

@jtherrmann Se este for um cabeçalho de autenticação, a maneira mais fácil de contornar o problema é definir um manipulador de autenticação em nível de sessão que simplesmente sempre coloca o cabeçalho desejado na solicitação.

Algum progresso ou consideração adicional foi dada a isso?
Estou tendo o mesmo problema.

@ethanroy Nenhuma consideração adicional além da minha sugestão de usar um manipulador de autenticação de nível de sessão, no comentário diretamente acima do seu.

Relacionado: se uma sessão redireciona e remove a autenticação, chamar get novamente reaplica a autenticação e usa o redirecionamento em cache. Então bata duas vezes e você entra. Comportamento pretendido?

>>> s = requests.Session()
>>> s.headers.update({"Authorization": "Token {}".format(API_TOKEN)})
>>> s.get(url)

<Response [403]>

>>> s.get(url)

<Response [200]>

@GregBakker Sim, ish. É uma confluência de comportamentos pretendidos. No entanto, esse bug indica que o 403 original não deveria acontecer.

@Lukasa quando você diz "a maneira mais fácil de contornar o problema é definir um manipulador de autenticação de nível de sessão", isso funciona hoje? Com base no que estou vendo no código, a resposta é não, mas suas palavras me fazem pensar se estou faltando alguma coisa. Você está falando sobre definir o atributo de autenticação da sessão, certo?

Sim, isso _deve_ funcionar.

@jwineinger então como você acabou contornando esse problema? ainda parece se comportar da mesma forma.

Existem duas soluções alternativas específicas do Nest.

Uma é passar o parâmetro auth com o access_token em vez de usar o cabeçalho Authorization. Eu encontrei isso em https://gist.github.com/tylerdave/409ffa08e1d47b1a1e23

Outra é salvar um dicionário com os cabeçalhos que você usaria, não siga os redirecionamentos e, em seguida, faça uma segunda solicitação passando os cabeçalhos novamente:

    headers = {'Authorization': 'Bearer ' + access_token, 'Content-Type': 'application/json'}
    initial_response = requests.get('https://developer-api.nest.com', headers=headers, allow_redirects=False)
    if initial_response.status_code == 307:
        api_response = requests.get(initial_response.headers['Location'], headers=headers, allow_redirects=False)

Encontrei o mesmo problema e o resolvi substituindo o método rebuild_auth em uma implementação requests.Session :

from requests import Session

class CustomSession(Session):
    def rebuild_auth(self, prepared_request, response):
        return

s = CustomSession()
s.get(url, auth=("username", "password"))

@ sigmavirus24 o que há de errado com a solução de @ gabriel-loo? Preocupações com segurança?

@ j08lue sim. Por favor, leia o tópico. Existem CVEs associados a não remover a autenticação antes de seguir redirecionamentos arbitrários para um novo domínio. Pense no problema desta forma:

Estou fazendo solicitações para api.github.com e um invasor consegue me fazer seguir um redirecionamento para another-domain.com que eles controlam e eu passo meu token com acesso de gravação para meus repositórios (incluindo solicitações) e então pode parecer que estou fazendo commits para solicitações quando, na verdade, eles estão fazendo esses commits por meio da API. Eles podem incluir códigos em Solicitações que enfraquecerão sua postura de segurança e possivelmente prejudicarão você ativamente. Isso é o que pode acontecer quando você envia incondicionalmente suas credenciais de autenticação em cada redirecionamento.

Mesmo assim, digamos que o redirecionamento não seja malicioso. Você se sente confortável em divulgar suas credenciais de um serviço para outra empresa ou serviço? O serviço original pode armazenar dados confidenciais para você, seus clientes ou qualquer outra coisa. Mesmo se o novo domínio para o qual você foi redirecionado não usar suas credenciais, mas potencialmente registrá-los como dados inesperados, alguém que os ataca e pode recuperar esses registros pode usar suas credenciais contra o domínio original se puderem descobrir onde eles pertencem. Você está realmente disposto a correr esse risco?

Obrigado pela ilustração, @ sigmavirus24. Se essa preocupação, em última análise, proíbe o encaminhamento de cabeçalhos confidenciais para redirecionamentos, por que este tópico ainda está aberto? Não consegui pensar em um erro mais apropriado do que o que você obteve (403), portanto, não há necessidade de ação de bug aqui, certo? Ou o que você tem em mente , @Lukasa?

Eu estava tendo esse problema recentemente ao trabalhar com uma API não pública. As preocupações com a segurança fazem totalmente sentido como motivo para remover a autenticação nos redirecionamentos. Acho que uma solução como a de @gabriel-loo é algo que as pessoas podem considerar se acreditarem que estão em um ambiente seguro o suficiente para isso. Ou o manipulador de nível de sessão. Ou encontre outra maneira de contornar ignorando o redirecionamento inteiramente conforme sugerido acima, se possível. Então, de acordo com a visão, isso não é realmente um bug.

No entanto, gastei mais tempo do que provavelmente precisava para ficar confuso sobre por que um punhado de outros clientes HTTP não-Python _did_ passou o cabeçalho de autenticação e estava funcionando bem quando isso não era. Uma sugestão: pode ser bom emitir um aviso via warnings aqui para deixar mais claro para os chamadores quando o cabeçalho está presente e sendo removido. Imagino que seja raro isso ser algo sobre o qual um chamador _não_ queira ser avisado.

@tlantz normalmente isso parece bastante razoável. Solicitações como um projeto (bem como urllib3, uma de suas dependências) causou uma quantidade significativa de ira quando emite qualquer tipo de aviso, seja por meio do módulo de avisos ou por meio de registro. Além disso, o módulo de avisos é para coisas sobre as quais as pessoas devem agir, por exemplo, não usar uma versão do Python que foi compilada com uma versão recente do OpenSSL.

Na maioria dos casos, esse comportamento não é tão problemático quanto, por exemplo, não conseguir verificar um certificado para uma conexão TLS. Isso obviamente não ajuda você ou qualquer outra pessoa que tenha expressado sua frustração genuína e válida sobre este assunto. Com isso em mente, eu me pergunto se não seria melhor tentar registrar isso no nível DEBUG . Se alguém estiver usando o registro (geralmente uma prática decente) e habilitar esse nível, ele aparecerá para eles. Além disso, dado o quão poucos os próprios registros de solicitações são, isso será bastante importante como um registro de depuração. Isso parece uma troca justa?

Sim, isso parece uma troca totalmente justa. Raciocinar em torno de warnings faz sentido para mim. Acho que quando você fica intrigado por cerca de 30 minutos, geralmente está adicionando logging torno de suas próprias coisas em DEBUG , então acho que uma mensagem DEBUG atingiria 95% dos casos em que as pessoas estão presas tentando descobrir o que não está funcionando.

Eu uso uma sessão para manter o cabeçalho de autorização, mas não está sendo enviado em um redirecionamento
solicitações (2.18.4)

Um colega de trabalho e eu passamos pelo menos algumas horas depurando um problema diretamente relacionado a esse comportamento. Meu caso de uso é redirecionar uma chamada de API de api.my-example-site.org para www.api.my-example-site.org . Os cabeçalhos estavam sendo removidos no redirecionamento.

Se for esse o comportamento pretendido (ou se não for alterado no futuro próximo), podemos pelo menos adicioná-lo à documentação? Eu li e reli os documentos tentando descobrir o que estava fazendo incorretamente e até li todo o código da classe Request . Se eu tivesse visto um aviso sobre esse comportamento na documentação, teria corrigido meu problema em alguns minutos (que é o tempo que levou depois de encontrar este tópico). Talvez estejamos lendo na parte errada da documentação, entretanto.

Olá @ndmeiri , temos uma Cabeçalhos personalizados . Se você acha que há um lugar melhor para colocá-lo, ficaremos felizes em revisar suas sugestões. Eu preferiria que mudássemos para uma edição separada ou PR, pois não está diretamente relacionado a este tíquete. Obrigado!

Olá @nateprewitt , obrigado por apontar a seção Custom Headers! Evidentemente, não pensei em verificar essa parte da documentação.

Acho que seria útil incluir também a chamada, ou uma referência à chamada, na seção sobre Autenticação . Embora esteja bastante ocupado no momento, considerarei abrir um PR quando as coisas se acalmarem para atualizar os documentos.

Se este for o comportamento pretendido

@ndmeiri Sim, o comportamento é não vazar suas credenciais de autenticação confidenciais para fontes potencialmente não confiáveis. (Só para ficar claro)

Parece que os "domínios confiáveis" de # 4983 não estão mais na implementação de sessions.py.

Na situação em que estou fazendo solicitações para um URL que _conhe_ redireciona para um URL diferente, mas seguro, e desejo habilitar o redirecionamento com persistência do cabeçalho de autorização, como faria isso, por favor?

como eu faria isso, por favor?

Você pode corrigir o método rebuild_auth . Isso funciona para mim: https://github.com/DHI-GRAS/earthdata-download/blob/master/earthdata_download/download.py#L27 -L49

@ j08lue Obrigado! Antes de seu comentário chegar, resolvi o problema definindo allow_redirects como False e adicionando código para seguir explicitamente alguns redirecionamentos específicos que são esperados em meu caso de uso. Esta é uma situação de curto prazo para mim, então espero que seja uma solução temporária adequada, mas é ótimo saber que existe uma maneira melhor de fazer isso a longo prazo, se necessário.

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

Questões relacionadas

iLaus picture iLaus  ·  3Comentários

cnicodeme picture cnicodeme  ·  3Comentários

ReimarBauer picture ReimarBauer  ·  4Comentários

thadeusb picture thadeusb  ·  3Comentários

remram44 picture remram44  ·  4Comentários