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.
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:
username
+ password
ser um parâmetro em request.body?username
+ password
aparecer no cabeçalho HTTP Basic Auth, eles devem ser duplicados no corpo da solicitação?username
+ password
e um cabeçalho HTTPBasicAuth com os detalhes do cliente?Obrigado por topar com isso.
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).client_secret
). As credenciais do usuário nunca devem estar em HTTP Basic Auth.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:
A menos que sejam explicitamente especificados, eles não devem estar presentes, correto?
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.
Correto.
É 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.
- 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:
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()
.
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:
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.
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.
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:
@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.
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
seclient_id
tiver sido fornecido como um kwarg?