Oauthlib: O aplicativo da web cliente não envia mais client_id

Criado em 8 set. 2018  ·  26Comentários  ·  Fonte: oauthlib/oauthlib

Encontrei uma regressão no mestre quando usado com as solicitações / solicitações-oauthlib desde que https://github.com/oauthlib/oauthlib/issues/495 foi mesclado. Está relacionado à concessão de autorização / aplicativo da web apenas.

O uso básico de requests-oauthlib é:

sess = OAuth2Session(client_id)
token = sess.fetch_token(token_url, client_secret=client_secret, authorization_response=request.url)

No entanto, desde as mudanças, client_id da sessão é ignorada. Acho que https://github.com/oauthlib/oauthlib/pull/505 corrigiu um caso de uso, mas quebrou outro. Devemos encontrar uma solução ganha-ganha.

solicitação de código de solicitações-oauthlib em https://github.com/requests/requests-oauthlib/blob/master/requests_oauthlib/oauth2_session.py#L196 -L198 e problema de oauthlib aqui
https://github.com/oauthlib/oauthlib/blame/master/oauthlib/oauth2/rfc6749/clients/web_application.py#L128.

Bug Discussion OAuth2-Client

Comentários muito úteis

Não veria como você desejaria substituir client_id por um valor diferente, então votaria por criar uma exceção se eles fossem diferentes.

Devemos, além disso, registrar um DeprecationWarning se client_id tiver sido fornecido como um kwarg?

Todos 26 comentários

Meu primeiro pensamento é reverter as mudanças em prepare_request_body para, por padrão, usar o self.client_id definido no construtor WebApplicationClient .
Em seguida, os documentos inline devem ser alterados de forma adequada para adicionar &client_id=xx à saída de prepare_request_body .

Finalmente, para substituir a correção original, sugerirei remover client_id dos args e adicionar um novo argumento a prepare_request_body como include_client=True/False para adicionar client_id e client_secret no corpo, ou não incluir ambos.

Pensamentos?

cutuque @Diaoul @skion @thedrow

A respeito:

Vou sugerir remover client_id do args e adicionar um novo argumento para prepare_request_body como include_client = True / False para adicionar client_id e client_secret no corpo, ou não incluir ambos.

Obrigado

Na verdade, eu acionei esse mesmo problema em um de meus testes, mas registrei-o contra requests / oauthlib aqui: https://github.com/requests/requests-oauthlib/issues/330

Acredito que o problema seja realmente culpa de requests_oauthlib. Seus documentos - na verdade, o primeiro exemplo em todos os documentos quando você carrega a página - suportam a especificação de client_id no construtor OAuth2Session . A lógica na linha 200 puxa o client_id dos kwargs, mas não tem um fallback para puxá-lo da instância WebApplicationClient já construída.

@jvanasco , o problema atual # 585 pode ser corrigido apenas no requests-oauthlib ou revertendo o PR # 505 do oauthlib. No entanto, nenhuma das soluções corrigirá o comportamento mencionado por @skion em seu comentário em https://github.com/oauthlib/oauthlib/pull/505#issuecomment -351221107

De acordo com a especificação, o parâmetro client_id deve ser enviado para clientes não autenticados, mas de preferência NÃO é enviado no corpo da solicitação de token para clientes confidenciais, pois nesse caso o mecanismo preferencial para autenticar o cliente é via autenticação HTTP Basic. No entanto, a classe WebApplication sempre o inclui (o que interrompe alguns servidores) e não oferece nenhum mecanismo para removê-lo.

Oauthlib deve fornecer uma maneira simples e elegante para que o requests-oauthlib resolva o problema. Se pudermos encontrar uma solução nesta discussão, será ótimo; porque isso é um grande bloqueador.

Permitir client_id=False em prepare_request_body() ajudaria a indicar que um client_id não deve ser enviado? Embora, mesmo assim, isso levaria a essa feiura perto da linha 126 :

client_id = None if client_id=False else self.client_id

Ah, entendo.

Existe um teste de unidade existente para quando o client_id deve ser enviado ou não? Se não, alguém tem uma lista que pode ser usada para gerar uma? Eu ficaria feliz em tentar consertar isso e o requests-oauthlib, porque está bloqueando parte do meu trabalho agora.

@JonathanHuot

Vou sugerir remover client_id do args e adicionar um novo argumento para prepare_request_body como include_client = True / False para adicionar client_id e client_secret no corpo, ou não incluir ambos.

Lendo isso de volta, na verdade gosto bastante da sua sugestão. Provavelmente podemos sair fazendo isso de uma forma ininterrupta, uma vez que a função já usa **kwargs .

Uma nota:

para adicionar client_id e client_secret no corpo

Visto que se trata de clientes públicos IIUC, não creio que client_secret esteja envolvido; é apenas client_id sendo adicionado ao corpo? Nesse caso, também consideraria renomear o novo parâmetro para include_client_id=True/False .

Nesse caso, também consideraria renomear o novo parâmetro para include_client_id = True / False.

De fato ! client_secret não está envolvido porque não está presente em WebApplicationClient .

@jvanasco , se você quiser fazer uma RP, acho que as mudanças deveriam ser:
1) Reverter https://github.com/oauthlib/oauthlib/pull/505
2) Mude a assinatura de prepare_request_body() para remover client_id e adicionar include_client_id=True/False ( True (padrão): adiciona self.client_id )

Então, requests-oauthlib terá a escolha em https://github.com/requests/requests-oauthlib/blob/master/requests_oauthlib/oauth2_session.py#L196-L211 para:
A) Incluir client_id sozinho no corpo

self._client.prepare_request_body(..)

B) Incluir client_id e client_secret em auth e não incluí-los no corpo (solução preferida de RFC)

self._client.prepare_request_body(include_client_id=False, ..)
auth = requests.auth.HTTPBasicAuth(client_id, client_secret)

C) Incluir client_id e client_secret no corpo (solução alternativa RFC)

self._client.prepare_request_body(client_secret=client_secret, ..)

Vou gerar RP para os dois projetos hoje.

Eu tenho praticamente um PR e testes feitos para OAuthlib. Eu tenho uma pergunta embora ...

client_id ainda deve ser permitido como kwarg? Isso é em parte para compatibilidade com versões anteriores, mas também para casos extremos. Como esse método foi um tanto quebrado, acho que pode valer a pena fazê-lo funcionar conforme o planejado (permitindo que prepare_request_body substitua o self.client_id) ou para gerar uma exceção no uso incorreto (como ao aumentar um erro se client_id for fornecido, mas não corresponder a self.client_id ).

Não veria como você desejaria substituir client_id por um valor diferente, então votaria por criar uma exceção se eles fossem diferentes.

Devemos, além disso, registrar um DeprecationWarning se client_id tiver sido fornecido como um kwarg?

PR # 593 enviado. Ele gera um Aviso de depreciação quando client_id é enviado e um ValueError se for diferente de self.client_id . Há também um novo teste que garante a conformidade com os três cenários detalhados de @JonathanHuot .

encontrei meu primeiro problema com o candidato de PR do requests-oauthlib enquanto escrevo alguns testes

SEMPRE invoca prepare_request_body com username=username, password=password . Isso parece errado. Espero que alguém aqui esteja mais familiarizado com o RFC e saiba as respostas para o seguinte:

  1. deve um username + password ser um parâmetro em request.body?
  2. se username + password aparecer no cabeçalho HTTP Basic Auth, eles devem ser duplicados no corpo da solicitação?
  3. É possível ter os parâmetros de corpo username + password e um cabeçalho HTTPBasicAuth com os detalhes do cliente?

Obrigado por topar com isso.

  1. username e password devem sempre estar presentes no corpo da solicitação. Eles devem ser usados apenas para concessão de senha, também conhecida como legado. Eles não devem ser usados ​​para outras concessões (implícita, código, credenciais de cliente).
  2. HTTP Basic Auth é opcional para credenciais de cliente (recomendado para clientes confidenciais, ou seja, com client_secret ). As credenciais do usuário nunca devem estar em HTTP Basic Auth.
  3. sim

Obrigado. Apenas para esclarecer duas coisas e sinta-se à vontade para falar comigo como se eu tivesse cinco anos. Quero ter certeza de que entendi isso e os testes corretamente:

  1. Nome de usuário e senha são usados ​​apenas em um determinado tipo de concessão que os exija. Se usados, eles só podem estar presentes no corpo da solicitação.

A menos que sejam explicitamente especificados, eles não devem estar presentes, correto?

  1. Se a autenticação básica do Http for apenas para credenciais do cliente, o request-oauthlib existente não deve ter o bloco que gera uma autenticação básica do combo nome de usuário e senha.

Por favor, perdoe-me por ser prolixo e obcecado por esses pequenos detalhes. Só quero ter certeza de obter o comportamento correto e poder escrever testes que garantam que não tenhamos outra regressão.

@jvanasco , posso falar sobre o RFC OAuth2, mas não tenho certeza de como requests-oauthlib e flask-oauthlib encaixam.

  1. sim

Correto.

  1. Sim, AFAIU não deve ter este bloco https://github.com/requests/requests-oauthlib/blob/master/requests_oauthlib/oauth2_session.py#L207 -L211.

É meu entendimento, entretanto, será bom confrontar com a realidade do campo; ou seja, as experiências de requests-oauthlib e de provedores públicos diferentes. Várias discussões de request-oauthlib https://github.com/requests/requests-oauthlib/issues/218 , https://github.com/requests/requests-oauthlib/issues/211 , https://github.com/requests / request-oauthlib / issues / 264 , já aconteceu.

Eu acredito que eles tiveram uma confusão entre client password e client secret que são na verdade duas expressões para a mesma coisa.
Se seguirmos o raciocínio por trás de https://github.com/requests/requests-oauthlib/pull/206 , o conteúdo do PR nunca deveria ter sido como adicionar HTTPAuth(username, password) mas deveria ser HTTPAuth(client_id, client_secret (a senha do cliente).

Poke @Lukasa , @chaosct , @ibuchanan que participou das discussões do requests-oauthlib.

Excelente! Muito obrigado. Achei que fosse isso, mas queria confirmar.

Acho que sei como quero estruturar as coisas de solicitações agora. Eu tenho um punhado de commits no projeto de requisições principais, então eu sei o que os mantenedores gostam de ver em PRs e funcionalidade.

  1. Nome de usuário e senha são usados ​​apenas em um determinado tipo de concessão que os exija. Se usados, eles só podem estar presentes no corpo da solicitação.

Sim, à primeira parte: só um determinado tipo de bolsa os exige. Mas na 2ª parte sobre como enviá-los no corpo da solicitação, a especificação diz:

Incluindo as credenciais do cliente no corpo da solicitação
usar os dois parâmetros NÃO É RECOMENDADO
e DEVE ser limitado a clientes incapazes de utilizar diretamente
o esquema de autenticação HTTP Basic ...

Mas para servidores, lê-se:

O servidor de autorização PODE suportar, incluindo
as credenciais do cliente no corpo da solicitação ...

Um cliente compatível não enviaria as credenciais no corpo da solicitação.
Mas, para alguns servidores parcialmente implementados, eles só os aceitarão no corpo da solicitação.
Se bem me lembro, meu PR resolveu essa confusão adicionando o cabeçalho auth,
então o cliente envia ambos.

Eu acredito que @JonathanHuot está correto sobre o segundo ponto.

Olá, @ibuchanan , as citações a que você está se referindo usam o termo client credentials . Devemos ter muito cuidado para não misturar o cliente com o proprietário do recurso (o usuário real).

As funções são explicadas claramente aqui rfc6749 # 1.1 .
Este client credentials refere-se a client_id e client_secret e o proprietário do recurso está se referindo a username e password . Esses não são intercambiáveis.

Portanto, ao ler o RFC com essas funções significa que um cliente compatível DEVE enviar credenciais de cliente ( client_id , client_secret ) em HTTP básico e DEVE enviar credenciais de usuário ( username , password ) no corpo da solicitação (nunca leia uma alternativa aqui); consulte rfc6749 # 4.3.2 .

Alguns servidores rejeitam o pedido (400) se o client_id estiver no pedido
corpo. Acho que o padrão deve ser o que é recomendado pela especificação.

OK. Acho que o PR atual para oauthlib satisfaz as preocupações acima - o sinalizador include_client_id permite explicitamente que client_id seja enviado ou não.

em termos de requests_oauthlib , isto é o que estou pensando:

  1. username e password só aparecerão no corpo (não como HTTP básico). Se esse comportamento for necessário para uma integração de servidor não compatível, o implementador pode enviar um argumento auth ou headers para fetch_token() .

  2. fornecer client_id no lugar correto é um pouco chato, mas acho que entendi a lógica e os casos de uso. isso definitivamente vai precisar de alguma revisão.

A pergunta que eu tenho para @JonathanHuot e @ibuchanan:

oauthlib 's OAuth2 Client e requests_oauthlib OAuth2Session não mantêm o client_secret e devem invocá-lo repetidamente. Esse não era o caso no OAuth1, e acho que esse era o problema real por trás do # 370. A RFC não mencionou a necessidade disso e não consegui encontrar nenhum histórico.

Para mim, faz sentido estender o cliente com o armazenamento de client_secret e começar a descontinuar a dependência de OAuth2Session de passar client_secret em fetch_token e request favor de usar o que está agora no próprio Cliente. (isso também resolveria algumas outras inconsistências relatadas em https://github.com/requests/requests-oauthlib/issues/264)

Eu mudei ligeiramente o PR para oauthlib (# 593) para padronizar as variáveis ​​de teste para nome de usuário / senha e client_id / client_secret. Acho que isso deve evitar erros ou confusão entre os dois no futuro.

A mudança proposta para requests-oauthlib: https://github.com/requests/requests-oauthlib/compare/master...jvanasco : fix-oauthlib_client_id

Este é um pouco mais drástico, porque olhando o código e os testes, parece que a biblioteca estava apenas tentando fazer todo tipo de coisa funcionar que não deveria.

A correção faz algumas coisas:

  1. A verificação de username e password ocorre apenas para LegacyApplicationClient instâncias - já que esse deve ser o único tipo necessário (correto?). Há uma seção sob a verificação que mescla o nome de usuário / senha no dicionário kwargs. No momento, ela está desdentada, porque os testes sugerem que outros clientes podem querer passar essa informação.

  2. A lógica para lidar com os cabeçalhos client_id / auth foi reescrita para garantir que os tipos corretos de autenticação aconteçam no lugar certo por padrão. Se um usuário deseja forçar as credenciais de outra maneira, ainda é possível.

  3. Pergunta: Existe alguma situação em que client_secret não seria aprovado? Não consigo pensar em nenhum, mas existem muitos fluxos oAuth.

Portanto, na versão requests-oauthlib:

| include_client_id | auth | comportamento |
| ------------------- | --------------- | -------- |
| Nenhum (padrão) | Nenhum (padrão) | crie um objeto Auth com client_id . Não envie client_id no corpo. Este é o comportamento padrão, porque o RFC o recomenda. |
| Nenhum (padrão) | presente | use o objeto Auth. invocar prepare_request_body do oauthlib com include_client_id=False |
| False | presente | use o objeto Auth. invocar prepare_request_body do oauthlib com include_client_id=False |
| True | presente | use o objeto Auth. invocar prepare_request_body do oauthlib com include_client_id=True |
| True | Nenhum (padrão) | crie um objeto Auth com client_id . invocar prepare_request_body do oauthlib com include_client_id=True |
| False | Nenhum (padrão) | crie um objeto Auth com client_id . invocar prepare_request_body do oauthlib com include_client_id=False |

ou declarado de outra forma:

  • criar um objeto de autenticação

    • não envie o ID do cliente no corpo:

    • (include_client_id = None, auth = None)

    • (include_client_id = False, auth = None)

    • envie o ID do cliente no corpo:

    • (include_client_id = True, auth = None)

  • use um objeto de autenticação explícito

    • não envie client_id no corpo:

    • (include_client_id = None, auth = authObject)

    • (include_client_id = False, auth = authObject)

    • envie o client_id no corpo:

    • (include_client_id = True, auth = authObject)

@jvanasco : parece muito bom no lado oauthlib e no lado dos pedidos-oauthlib.

Sobre sua 3. pergunta:

Você pode ter clientes públicos sem client_secret (ou vazio, que está próximo do ponto de vista da RFC); portanto, a API python deve oferecer suporte a "nenhum segredo".

O caso de uso do mundo real costuma ser para aplicativos nativos onde você prefere usar o código de autorização, mas não pode garantir a segurança em torno de manter o segredo seguro, portanto, você aceita um client_id sem client_secret (ou client_secret vazios que são idênticos para o RFC); ou você também tem outro RFC PKCE disponível (consulte https://oauth.net/2/pkce/). Mas o último ainda não foi implementado no lado oauthlib .

obrigado. Vou adicionar alguns casos de teste para garantir que podemos enviar client_id sem client_secret . Desculpe por fazer tantas perguntas, existem tantas implementações corretas possíveis desta especificação (e ainda mais implementações quebradas que precisam funcionar).

@JonathanHuot a implementação existente não suporta o envio de uma string vazia para client_secret . Ele é removido nesta lógica https://github.com/oauthlib/oauthlib/blob/master/oauthlib/oauth2/rfc6749/parameters.py#L90 -L125 - especificamente linha 122

Apoiá-lo pode ser adicionar algo assim logo após a rotina:

if ('client_secret' in kwargs) and ('client_secret' not in params):
    if kwargs['client_secret'] == '':
        params.append((unicode_type('client_secret'), kwargs['client_secret']))

Isso enviaria uma string vazia para client_secret quando o segredo for uma string vazia, mas não enviaria client_secret se o valor for None .

Acho que vale a pena apoiar isso, porque se a RFC oferecer suporte a qualquer uma das duas variantes ... é provável que haja muitas implementações quebradas que suportam apenas uma variante.

Este problema original foi corrigido no oauthlib. No entanto, o comportamento ainda existe até que https://github.com/requests/requests-oauthlib/pull/331 seja corrigido.

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