Oauthlib: Клиентское веб-приложение больше не отправляет client_id

Созданный на 8 сент. 2018  ·  26Комментарии  ·  Источник: oauthlib/oauthlib

Я обнаружил регресс в мастере при использовании с request / requests-oauthlib, поскольку https://github.com/oauthlib/oauthlib/issues/495 был объединен. Это относится только к разрешению / веб-приложению.

Основное использование requests-oauthlib:

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

Однако после внесения изменений client_id сеанса игнорируется. Я думаю, что https://github.com/oauthlib/oauthlib/pull/505 исправил один вариант использования, но сломал другой. Мы должны найти беспроигрышное решение.

Вызов кода requests-oauthlib по адресу https://github.com/requests/requests-oauthlib/blob/master/requests_oauthlib/oauth2_session.py#L196 -L198 и проблема oauthlib здесь
https://github.com/oauthlib/oauthlib/blame/master/oauthlib/oauth2/rfc6749/clients/web_application.py#L128.

Bug Discussion OAuth2-Client

Самый полезный комментарий

Вы бы не увидели, как вы когда-нибудь захотите переопределить client_id с другим значением, поэтому проголосовали бы за создание исключения, если они отличаются.

Должны ли мы дополнительно регистрировать DeprecationWarning если client_id вообще был предоставлен как kwarg?

Все 26 Комментарий

Моя первая мысль - отменить изменения в prepare_request_body чтобы по умолчанию использовать self.client_id установленный в конструкторе WebApplicationClient .
Затем следует изменить встроенные документы, чтобы добавить &client_id=xx к выходу prepare_request_body .

Наконец, чтобы заменить исходное исправление, я предлагаю удалить client_id из аргументов и добавить новый аргумент в prepare_request_body например include_client=True/False чтобы добавить client_id и client_secret в теле, или не включать их оба.

Мысли?

тыкай @Diaoul @skion @thedrow

Что о:

Я предлагаю удалить client_id из аргументов и добавить новый аргумент в prepare_request_body, например include_client = True / False, чтобы добавить client_id и client_secret в тело или не включать их оба.

Спасибо

Я действительно вызвал эту же проблему в одном из своих тестов, но подал ее против запросов / oauthlib здесь: https://github.com/requests/requests-oauthlib/issues/330

Я считаю, что проблема на самом деле связана с request_oauthlib. Их документы - фактически первый пример во всей их документации при загрузке страницы - поддерживают указание client_id в конструкторе OAuth2Session . Логика в строке 200 извлекает client_id из kwargs, но не имеет запасного варианта для извлечения его из уже WebApplicationClient экземпляра

@jvanasco , текущая проблема # 585 может быть исправлена ​​только с помощью request-oauthlib или путем восстановления PR # 505 oauthlib. Однако ни одно из решений не исправит поведение, упомянутое @skion в его комментарии на https://github.com/oauthlib/oauthlib/pull/505#issuecomment -351221107.

Согласно спецификации, параметр client_id должен быть отправлен для неаутентифицированных клиентов, но предпочтительно НЕ отправляется в теле запроса токена для конфиденциальных клиентов, поскольку в этом случае предпочтительный механизм аутентификации клиента - через HTTP Basic auth. Однако класс WebApplication всегда включает его (что ломает некоторые серверы) и не предлагает механизма для его удаления.

Oauthlib должен предоставлять элегантный и простой способ для request-oauthlib решить проблему. Если мы сможем найти решение в этом обсуждении, это будет здорово; потому что это огромный блокиратор.

Будет ли разрешено client_id=False в prepare_request_body() help указывать, что client_id не нужно отправлять? Хотя даже если это так, это приведет к этому уродству около строки 126 :

client_id = None if client_id=False else self.client_id

Ах я вижу.

Есть ли существующий модульный тест, когда нужно отправлять client_id, а не нет? Если нет, есть ли у кого-нибудь список, который можно использовать для его создания? Я был бы счастлив попытаться исправить это и запросить-oauthlib, потому что он блокирует часть моей работы прямо сейчас.

@JonathanHuot

Я предлагаю удалить client_id из аргументов и добавить новый аргумент в prepare_request_body, например include_client = True / False, чтобы добавить client_id и client_secret в тело или не включать их оба.

Читая это в ответ, мне действительно нравится ваше предложение. Мы, вероятно, сможем избежать этого, сделав это безотказно, поскольку функция уже принимает **kwargs .

Одна нота:

чтобы добавить client_id и client_secret в тело

Поскольку речь идет о IIUC публичных клиентов, я бы не подумал, что здесь задействован client_secret ; это просто client_id добавляется в тело? В этом случае я бы также подумал о переименовании нового параметра в include_client_id=True/False .

В этом случае я бы также подумал о переименовании нового параметра в include_client_id = True / False.

Действительно ! client_secret не участвует, так как его нет в WebApplicationClient .

@jvanasco , если вы хотите сделать пиар, я думаю, что изменения должны быть такими:
1) Вернуть https://github.com/oauthlib/oauthlib/pull/505
2) Измените подпись prepare_request_body() чтобы удалить client_id и добавить include_client_id=True/False ( True (по умолчанию): добавляет self.client_id )

Затем в https://github.com/requests/requests-oauthlib/blob/master/requests_oauthlib/oauth2_session.py#L196-L211 будет выбор:
A) Включите client_id только в тело

self._client.prepare_request_body(..)

Б) Включите client_id и client_secret в auth и не включайте их в основной текст (предпочтительное решение RFC)

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

C) Включите client_id и client_secret в тело (альтернативное решение RFC)

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

Сегодня я создам PR для обоих проектов.

У меня в значительной степени есть PR и тесты для OAuthlib. Но у меня есть вопрос ...

Следует ли еще разрешить client_id в качестве kwarg? Это частично для обратной совместимости, но также и для крайних случаев. Поскольку этот метод был несколько сломан, я думаю, что может быть стоит либо заставить его работать по назначению (например, разрешить ему в prepare_request_body переопределить self.client_id), либо поднять исключение при неправильном использовании (как при повышении ошибка, если client_id указан, но не соответствует self.client_id ).

Вы бы не увидели, как вы когда-нибудь захотите переопределить client_id с другим значением, поэтому проголосовали бы за создание исключения, если они отличаются.

Должны ли мы дополнительно регистрировать DeprecationWarning если client_id вообще был предоставлен как kwarg?

PR № 593 отправлен. Он вызывает DeprecationWarning при отправке client_id и ValueError, если он отличается от self.client_id . Также есть новый тест, который обеспечивает соответствие трём сценариям, подробно описанным @JonathanHuot .

столкнулся с моей первой проблемой с кандидатом на PR request-oauthlib, когда я писал несколько тестов

Он ВСЕГДА вызывает prepare_request_body с username=username, password=password . Это кажется неправильным. Я надеюсь, что кто-то здесь более знаком с RFC и знает ответы на следующие вопросы:

  1. должен ли username + password вообще когда-либо быть параметром в request.body?
  2. если username + password появляется в заголовке HTTP Basic Auth, должны ли они дублироваться в теле запроса?
  3. Возможно ли иметь как параметры тела username + password и заголовок HTTPBasicAuth с данными клиента?

Спасибо, что столкнулись с этим.

  1. username и password всегда должны присутствовать в теле запроса. Они должны использоваться только для предоставления пароля, известного как устаревший. Их нельзя использовать для других грантов (неявных, кодовых, учетных данных клиента).
  2. Базовая аутентификация HTTP не является обязательной для учетных данных клиента (рекомендуется для конфиденциальных клиентов, т.е. с client_secret ). Учетные данные пользователя никогда не должны быть в HTTP Basic Auth.
  3. да

Спасибо. Просто чтобы прояснить две вещи, и, пожалуйста, не стесняйтесь говорить со мной, как будто мне пять. Я хочу убедиться, что правильно понял это и тесты:

  1. Имя пользователя и пароль используются только в определенных типах грантов, для которых они необходимы. Если они используются, они могут присутствовать только в теле запроса.

Если они явно не указаны, их не должно быть, верно?

  1. Если базовая аутентификация Http предназначена только для учетных данных клиента, тогда в существующем request-oauthlib не должно быть блока, который генерирует базовую аутентификацию из комбинации имени пользователя и пароля.

Пожалуйста, простите меня за многословность и зацикленность на этих мелких деталях. Я просто хочу убедиться, что у j правильное поведение, и могу написать тесты, которые гарантируют, что мы не получим еще одну регрессию.

@jvanasco , я могу рассказать о RFC OAuth2, однако я не уверен, как сочетаются requests-oauthlib и flask-oauthlib .

  1. да

Верный.

  1. Да, AFAIU у него не должно быть этого блока https://github.com/requests/requests-oauthlib/blob/master/requests_oauthlib/oauth2_session.py#L207 -L211.

Это я понимаю, однако было бы хорошо сопоставить с реальностью поля; то есть запросы-oauthlib и различные публичные провайдеры. Множественные обсуждения запросов-oauthlib https://github.com/requests/requests-oauthlib/issues/218 , https://github.com/requests/requests-oauthlib/issues/211 , https://github.com/requests / requests-oauthlib / issues / 264 , уже произошло.

Я считаю, что у них была путаница между client password и client secret которые на самом деле являются двумя формулировками для одного и того же.
Если мы будем следовать логике https://github.com/requests/requests-oauthlib/pull/206 , содержание PR никогда не должно было быть похоже на добавление HTTPAuth(username, password) а должно было быть HTTPAuth(client_id, client_secret (пароль клиента).

Poke @Lukasa , @chaosct , @ibuchanan, которые участвовали в обсуждениях request-oauthlib.

Большой! Спасибо. Я думал, что это то, что происходит, но хотел подтвердить.

Думаю, теперь я знаю, как я хочу структурировать запросы. У меня есть несколько коммитов в основном проекте запросов, поэтому я знаю, что сопровождающие хотят видеть в PR и функциональности.

  1. Имя пользователя и пароль используются только в определенных типах грантов, для которых они необходимы. Если они используются, они могут присутствовать только в теле запроса.

Да, в отношении первой части: они требуются только для определенного вида гранта. Но во второй части об отправке их в теле запроса в спецификации говорится:

Включение учетных данных клиента в тело запроса
использование двух параметров НЕ РЕКОМЕНДУЕТСЯ
и ДОЛЖЕН быть ограничен клиентами, которые не могут напрямую использовать
схема аутентификации HTTP Basic ...

Но для серверов это гласит:

Сервер авторизации МОЖЕТ поддерживать, в том числе
учетные данные клиента в теле запроса ...

Соответствующий клиент не будет отправлять учетные данные в теле запроса.
Но для некоторых частично реализованных серверов они будут принимать их только в теле запроса.
Если я правильно помню, мой PR решил эту путаницу, добавив заголовок auth,
поэтому клиент отправляет оба.

Я считаю, что @JonathanHuot прав насчет второго пункта.

Привет, @ibuchanan , в цитатах, на которые вы ссылаетесь, используется термин client credentials . Мы должны быть очень осторожны, чтобы не путать клиента с владельцем ресурса (фактическим пользователем).

Роли четко объяснены здесь rfc6749 # 1.1 .
Этот client credentials относится к client_id и client_secret а владелец ресурса ссылается на username и password . Они не взаимозаменяемы.

Итак, чтение RFC с этими ролями означает, что совместимый клиент ДОЛЖЕН отправлять учетные данные клиента ( client_id , client_secret ) в HTTP Basic и ДОЛЖЕН отправлять учетные данные пользователя ( username , password ) в теле запроса (никогда не читайте здесь альтернативу); см. rfc6749 # 4.3.2 .

Некоторые серверы отклоняют запрос (400), если client_id находится в запросе
тело. Я думаю, что по умолчанию должно быть то, что рекомендовано спецификацией.

Ok. Я думаю, что текущий PR для oauthlib удовлетворяет вышеуказанные проблемы - флаг include_client_id явно разрешает отправку client_id или нет.

с точки зрения requests_oauthlib , вот что я думаю:

  1. username и password появятся только в теле (не как HTTP Basic). Если такое поведение необходимо для несовместимой серверной интеграции, разработчик может отправить аргумент auth или headers в fetch_token() .

  2. предоставление client_id в правильном месте немного раздражает, но я думаю, что у меня есть логика и варианты использования. это обязательно потребует некоторого обзора.

У меня вопрос к @JonathanHuot и @ibuchanan :

oauthlib OAuth2 Client и requests_oauthlib OAuth2Session не сохраняют client_secret и должны вызывать его повторно. В случае с OAuth1 этого не было, и я думаю, что это была реальная проблема, стоящая за № 370. RFC не упоминал о необходимости этого, и я не смог найти никакой истории.

Для меня имеет смысл расширить клиент, сохранив client_secret , и начать отказываться от использования OAuth2Session при передаче client_secret на fetch_token и request в пользу использования его сейчас в самом клиенте. (это также устранит некоторые другие несоответствия, указанные в https://github.com/requests/requests-oauthlib/issues/264)

Я немного изменил PR для oauthlib (# 593), чтобы стандартизировать тестовые переменные для имени пользователя / пароля и client_id / client_secret. Я думаю, это должно предотвратить путаницу между ними в будущем.

Предлагаемое изменение для requests-oauthlib: https://github.com/requests/requests-oauthlib/compare/master...jvanasco : fix-oauthlib_client_id

Это немного более радикально, потому что, глядя на код и тесты, кажется, что библиотека просто пыталась заставить работать все виды вещей, которые не должны работать.

Исправление делает несколько вещей:

  1. Проверка на username и password происходит только для экземпляров LegacyApplicationClient - так как это должно быть единственно необходимым (правильно?). Под проверкой есть раздел, который объединяет имя пользователя / пароль в kwargs dict. В настоящее время он не используется, потому что тесты показывают, что другие клиенты могут захотеть передать эту информацию.

  2. Логика обработки заголовков client_id / auth была переписана, чтобы обеспечить правильные типы аутентификации по умолчанию в нужном месте. Если пользователь хочет принудительно ввести учетные данные другим способом, это все еще возможно.

  3. Вопрос: Есть ли ситуация, когда client_secret не будет передан? Я не могу вспомнить ни одного, но существует множество потоков oAuth.

Итак, в версии requests-oauthlib:

| include_client_id | auth | поведение |
| ------------------- | --------------- | -------- |
| Нет (по умолчанию) | Нет (по умолчанию) | создайте объект Auth с помощью client_id . Не отправляйте client_id в теле. Это поведение по умолчанию, потому что это рекомендуется RFC. |
| Нет (по умолчанию) | настоящее | используйте объект Auth. вызвать prepare_request_body oauthlib с помощью include_client_id=False |
| Ложь | настоящее | используйте объект Auth. вызвать prepare_request_body oauthlib с помощью include_client_id=False |
| Правда | настоящее | используйте объект Auth. вызвать prepare_request_body oauthlib с помощью include_client_id=True |
| Правда | Нет (по умолчанию) | создайте объект Auth с помощью client_id . вызвать prepare_request_body oauthlib с помощью include_client_id=True |
| Ложь | Нет (по умолчанию) | создайте объект Auth с client_id . вызвать prepare_request_body oauthlib с помощью include_client_id=False |

или указано иное:

  • создать объект аутентификации

    • не отправлять идентификатор клиента в теле:

    • (include_client_id = Нет, auth = Нет)

    • (include_client_id = False, auth = None)

    • отправьте идентификатор клиента в теле:

    • (include_client_id = True, auth = None)

  • использовать явный объект аутентификации

    • не отправляйте client_id в теле:

    • (include_client_id = Нет, auth = authObject)

    • (include_client_id = False, auth = authObject)

    • отправьте client_id в теле:

    • (include_client_id = True, auth = authObject)

@jvanasco : очень хорошо выглядит на стороне oauthlib и requests-oauthlib.

По поводу вашего 3. вопроса:

У вас могут быть общедоступные клиенты без client_secret (или пустые, что близко с точки зрения RFC); поэтому API-интерфейс python должен поддерживать «не секрет».

Реальный вариант использования часто используется для собственных приложений, в которых вы предпочитаете использовать код авторизации, но вы не можете гарантировать безопасность, сохраняя секрет в безопасности, поэтому вы либо принимаете client_id без client_secret (или пустой client_secret , идентичный RFC); или у вас есть еще один доступный RFC PKCE (см. https://oauth.net/2/pkce/). Но последнее пока не реализовано на стороне oauthlib .

Благодарю. Я добавлю несколько тестовых примеров, чтобы убедиться, что мы можем отправить client_id без client_secret . Извините за то, что задаю так много вопросов, существует так много возможных правильных реализаций этой спецификации (и даже больше сломанных реализаций, которые должны работать).

@JonathanHuot существующая реализация не поддерживает отправку пустой строки для client_secret . Он удален в этой логике https://github.com/oauthlib/oauthlib/blob/master/oauthlib/oauth2/rfc6749/parameters.py#L90 -L125 - в частности, строка 122

В поддержку этого можно добавить что-то вроде этого сразу после этой процедуры:

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

Это отправит пустую строку для client_secret когда секрет является пустой строкой, но не отправит client_secret если значение равно None .

Я думаю, что стоит поддержать это, потому что, если RFC поддерживает любой из двух вариантов ... вероятно, будет много неработающих реализаций, которые поддерживают только один вариант.

Эта исходная проблема исправлена ​​в oauthlib. Однако поведение сохраняется до тех пор, пока https://github.com/requests/requests-oauthlib/pull/331 не будет исправлен.

Была ли эта страница полезной?
0 / 5 - 0 рейтинги