aboutsummaryrefslogtreecommitdiffstats
path: root/contrib/python/requests-oauthlib
diff options
context:
space:
mode:
authoralexv-smirnov <alex@ydb.tech>2023-12-01 12:02:50 +0300
committeralexv-smirnov <alex@ydb.tech>2023-12-01 13:28:10 +0300
commit0e578a4c44d4abd539d9838347b9ebafaca41dfb (patch)
treea0c1969c37f818c830ebeff9c077eacf30be6ef8 /contrib/python/requests-oauthlib
parent84f2d3d4cc985e63217cff149bd2e6d67ae6fe22 (diff)
downloadydb-0e578a4c44d4abd539d9838347b9ebafaca41dfb.tar.gz
Change "ya.make"
Diffstat (limited to 'contrib/python/requests-oauthlib')
-rw-r--r--contrib/python/requests-oauthlib/.dist-info/METADATA245
-rw-r--r--contrib/python/requests-oauthlib/.dist-info/top_level.txt1
-rw-r--r--contrib/python/requests-oauthlib/AUTHORS.rst25
-rw-r--r--contrib/python/requests-oauthlib/LICENSE15
-rw-r--r--contrib/python/requests-oauthlib/README.rst58
-rw-r--r--contrib/python/requests-oauthlib/requests_oauthlib/__init__.py19
-rw-r--r--contrib/python/requests-oauthlib/requests_oauthlib/compliance_fixes/__init__.py10
-rw-r--r--contrib/python/requests-oauthlib/requests_oauthlib/compliance_fixes/douban.py17
-rw-r--r--contrib/python/requests-oauthlib/requests_oauthlib/compliance_fixes/ebay.py23
-rw-r--r--contrib/python/requests-oauthlib/requests_oauthlib/compliance_fixes/facebook.py33
-rw-r--r--contrib/python/requests-oauthlib/requests_oauthlib/compliance_fixes/fitbit.py25
-rw-r--r--contrib/python/requests-oauthlib/requests_oauthlib/compliance_fixes/instagram.py26
-rw-r--r--contrib/python/requests-oauthlib/requests_oauthlib/compliance_fixes/mailchimp.py23
-rw-r--r--contrib/python/requests-oauthlib/requests_oauthlib/compliance_fixes/plentymarkets.py29
-rw-r--r--contrib/python/requests-oauthlib/requests_oauthlib/compliance_fixes/slack.py37
-rw-r--r--contrib/python/requests-oauthlib/requests_oauthlib/compliance_fixes/weibo.py15
-rw-r--r--contrib/python/requests-oauthlib/requests_oauthlib/oauth1_auth.py117
-rw-r--r--contrib/python/requests-oauthlib/requests_oauthlib/oauth1_session.py400
-rw-r--r--contrib/python/requests-oauthlib/requests_oauthlib/oauth2_auth.py37
-rw-r--r--contrib/python/requests-oauthlib/requests_oauthlib/oauth2_session.py540
-rw-r--r--contrib/python/requests-oauthlib/tests/__init__.py0
-rw-r--r--contrib/python/requests-oauthlib/tests/test.bin1
-rw-r--r--contrib/python/requests-oauthlib/tests/test_compliance_fixes.py334
-rw-r--r--contrib/python/requests-oauthlib/tests/test_core.py170
-rw-r--r--contrib/python/requests-oauthlib/tests/test_oauth1_session.py348
-rw-r--r--contrib/python/requests-oauthlib/tests/test_oauth2_auth.py54
-rw-r--r--contrib/python/requests-oauthlib/tests/test_oauth2_session.py527
-rw-r--r--contrib/python/requests-oauthlib/tests/ya.make28
-rw-r--r--contrib/python/requests-oauthlib/ya.make45
29 files changed, 3202 insertions, 0 deletions
diff --git a/contrib/python/requests-oauthlib/.dist-info/METADATA b/contrib/python/requests-oauthlib/.dist-info/METADATA
new file mode 100644
index 0000000000..975ce567fc
--- /dev/null
+++ b/contrib/python/requests-oauthlib/.dist-info/METADATA
@@ -0,0 +1,245 @@
+Metadata-Version: 2.1
+Name: requests-oauthlib
+Version: 1.3.1
+Summary: OAuthlib authentication support for Requests.
+Home-page: https://github.com/requests/requests-oauthlib
+Author: Kenneth Reitz
+Author-email: me@kennethreitz.com
+License: ISC
+Platform: UNKNOWN
+Classifier: Development Status :: 5 - Production/Stable
+Classifier: Intended Audience :: Developers
+Classifier: Natural Language :: English
+Classifier: License :: OSI Approved :: BSD License
+Classifier: Programming Language :: Python
+Classifier: Programming Language :: Python :: 2
+Classifier: Programming Language :: Python :: 2.7
+Classifier: Programming Language :: Python :: 3
+Classifier: Programming Language :: Python :: 3.4
+Classifier: Programming Language :: Python :: 3.5
+Classifier: Programming Language :: Python :: 3.6
+Classifier: Programming Language :: Python :: 3.7
+Classifier: Programming Language :: Python :: 3.8
+Classifier: Programming Language :: Python :: 3.9
+Classifier: Programming Language :: Python :: Implementation :: CPython
+Classifier: Programming Language :: Python :: Implementation :: PyPy
+Requires-Python: >=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*
+Description-Content-Type: text/x-rst
+License-File: LICENSE
+Requires-Dist: oauthlib (>=3.0.0)
+Requires-Dist: requests (>=2.0.0)
+Provides-Extra: rsa
+Requires-Dist: oauthlib[signedtoken] (>=3.0.0) ; extra == 'rsa'
+
+Requests-OAuthlib |build-status| |coverage-status| |docs|
+=========================================================
+
+This project provides first-class OAuth library support for `Requests <http://python-requests.org>`_.
+
+The OAuth 1 workflow
+--------------------
+
+OAuth 1 can seem overly complicated and it sure has its quirks. Luckily,
+requests_oauthlib hides most of these and let you focus at the task at hand.
+
+Accessing protected resources using requests_oauthlib is as simple as:
+
+.. code-block:: pycon
+
+ >>> from requests_oauthlib import OAuth1Session
+ >>> twitter = OAuth1Session('client_key',
+ client_secret='client_secret',
+ resource_owner_key='resource_owner_key',
+ resource_owner_secret='resource_owner_secret')
+ >>> url = 'https://api.twitter.com/1/account/settings.json'
+ >>> r = twitter.get(url)
+
+Before accessing resources you will need to obtain a few credentials from your
+provider (e.g. Twitter) and authorization from the user for whom you wish to
+retrieve resources for. You can read all about this in the full
+`OAuth 1 workflow guide on RTD <https://requests-oauthlib.readthedocs.io/en/latest/oauth1_workflow.html>`_.
+
+The OAuth 2 workflow
+--------------------
+
+OAuth 2 is generally simpler than OAuth 1 but comes in more flavours. The most
+common being the Authorization Code Grant, also known as the WebApplication
+flow.
+
+Fetching a protected resource after obtaining an access token can be extremely
+simple. However, before accessing resources you will need to obtain a few
+credentials from your provider (e.g. Google) and authorization from the user
+for whom you wish to retrieve resources for. You can read all about this in the
+full `OAuth 2 workflow guide on RTD <https://requests-oauthlib.readthedocs.io/en/latest/oauth2_workflow.html>`_.
+
+Installation
+-------------
+
+To install requests and requests_oauthlib you can use pip:
+
+.. code-block:: bash
+
+ $ pip install requests requests_oauthlib
+
+.. |build-status| image:: https://github.com/requests/requests-oauthlib/actions/workflows/run-tests.yml/badge.svg
+ :target: https://github.com/requests/requests-oauthlib/actions
+.. |coverage-status| image:: https://img.shields.io/coveralls/requests/requests-oauthlib.svg
+ :target: https://coveralls.io/r/requests/requests-oauthlib
+.. |docs| image:: https://readthedocs.org/projects/requests-oauthlib/badge/
+ :alt: Documentation Status
+ :scale: 100%
+ :target: https://requests-oauthlib.readthedocs.io/
+
+
+History
+-------
+
+v1.3.1 (21 January 2022)
+++++++++++++++++++++++++
+
+- Add initial support for OAuth Mutual TLS (draft-ietf-oauth-mtls)
+- Add eBay compliance fix
+- Add Spotify OAuth 2 Tutorial
+- Add support for python 3.8, 3.9
+- Fixed LinkedIn Compliance Fixes
+- Fixed ReadTheDocs Documentation and sphinx errors
+- Moved pipeline to GitHub Actions
+
+v1.3.0 (6 November 2019)
+++++++++++++++++++++++++
+
+- Instagram compliance fix
+- Added ``force_querystring`` argument to fetch_token() method on OAuth2Session
+
+v1.2.0 (14 January 2019)
+++++++++++++++++++++++++
+
+- This project now depends on OAuthlib 3.0.0 and above. It does **not** support
+ versions of OAuthlib before 3.0.0.
+- Updated oauth2 tests to use 'sess' for an OAuth2Session instance instead of `auth`
+ because OAuth2Session objects and methods acceept an `auth` paramether which is
+ typically an instance of `requests.auth.HTTPBasicAuth`
+- `OAuth2Session.fetch_token` previously tried to guess how and where to provide
+ "client" and "user" credentials incorrectly. This was incompatible with some
+ OAuth servers and incompatible with breaking changes in oauthlib that seek to
+ correctly provide the `client_id`. The older implementation also did not raise
+ the correct exceptions when username and password are not present on Legacy
+ clients.
+- Avoid automatic netrc authentication for OAuth2Session.
+
+v1.1.0 (9 January 2019)
++++++++++++++++++++++++
+
+- Adjusted version specifier for ``oauthlib`` dependency: this project is
+ not yet compatible with ``oauthlib`` 3.0.0.
+- Dropped dependency on ``nose``.
+- Minor changes to clean up the code and make it more readable/maintainable.
+
+v1.0.0 (4 June 2018)
+++++++++++++++++++++
+
+- **Removed support for Python 2.6 and Python 3.3.**
+ This project now supports Python 2.7, and Python 3.4 and above.
+- Added several examples to the documentation.
+- Added plentymarkets compliance fix.
+- Added a ``token`` property to OAuth1Session, to match the corresponding
+ ``token`` property on OAuth2Session.
+
+v0.8.0 (14 February 2017)
++++++++++++++++++++++++++
+
+- Added Fitbit compliance fix.
+- Fixed an issue where newlines in the response body for the access token
+ request would cause errors when trying to extract the token.
+- Fixed an issue introduced in v0.7.0 where users passing ``auth`` to several
+ methods would encounter conflicts with the ``client_id`` and
+ ``client_secret``-derived auth. The user-supplied ``auth`` argument is now
+ used in preference to those options.
+
+v0.7.0 (22 September 2016)
+++++++++++++++++++++++++++
+
+- Allowed ``OAuth2Session.request`` to take the ``client_id`` and
+ ``client_secret`` parameters for the purposes of automatic token refresh,
+ which may need them.
+
+v0.6.2 (12 July 2016)
++++++++++++++++++++++
+
+- Use ``client_id`` and ``client_secret`` for the Authorization header if
+ provided.
+- Allow explicit bypass of the Authorization header by setting ``auth=False``.
+- Pass through the ``proxies`` kwarg when refreshing tokens.
+- Miscellaneous cleanups.
+
+v0.6.1 (19 February 2016)
++++++++++++++++++++++++++
+
+- Fixed a bug when sending authorization in headers with no username and
+ password present.
+- Make sure we clear the session token before obtaining a new one.
+- Some improvements to the Slack compliance fix.
+- Avoid timing problems around token refresh.
+- Allow passing arbitrary arguments to requests when calling
+ ``fetch_request_token`` and ``fetch_access_token``.
+
+v0.6.0 (14 December 2015)
++++++++++++++++++++++++++
+
+- Add compliance fix for Slack.
+- Add compliance fix for Mailchimp.
+- ``TokenRequestDenied`` exceptions now carry the entire response, not just the
+ status code.
+- Pass through keyword arguments when refreshing tokens automatically.
+- Send authorization in headers, not just body, to maximize compatibility.
+- More getters/setters available for OAuth2 session client values.
+- Allow sending custom headers when refreshing tokens, and set some defaults.
+
+
+v0.5.0 (4 May 2015)
++++++++++++++++++++
+- Fix ``TypeError`` being raised instead of ``TokenMissing`` error.
+- Raise requests exceptions on 4XX and 5XX responses in the OAuth2 flow.
+- Avoid ``AttributeError`` when initializing the ``OAuth2Session`` class
+ without complete client information.
+
+v0.4.2 (16 October 2014)
+++++++++++++++++++++++++
+- New ``authorized`` property on OAuth1Session and OAuth2Session, which allows
+ you to easily determine if the session is already authorized with OAuth tokens
+ or not.
+- New ``TokenMissing`` and ``VerifierMissing`` exception classes for OAuth1Session:
+ this will make it easier to catch and identify these exceptions.
+
+v0.4.1 (6 June 2014)
+++++++++++++++++++++
+- New install target ``[rsa]`` for people using OAuth1 RSA-SHA1 signature
+ method.
+- Fixed bug in OAuth2 where supplied state param was not used in auth url.
+- OAuth2 HTTPS checking can be disabled by setting environment variable
+ ``OAUTHLIB_INSECURE_TRANSPORT``.
+- OAuth1 now re-authorize upon redirects.
+- OAuth1 token fetching now raise a detailed error message when the
+ response body is incorrectly encoded or the request was denied.
+- Added support for custom OAuth1 clients.
+- OAuth2 compliance fix for Sina Weibo.
+- Multiple fixes to facebook compliance fix.
+- Compliance fixes now re-encode body properly as bytes in Python 3.
+- Logging now properly done under ``requests_oauthlib`` namespace instead
+ of piggybacking on oauthlib namespace.
+- Logging introduced for OAuth1 auth and session.
+
+v0.4.0 (29 September 2013)
+++++++++++++++++++++++++++
+- OAuth1Session methods only return unicode strings. #55.
+- Renamed requests_oauthlib.core to requests_oauthlib.oauth1_auth for consistency. #79.
+- Added Facebook compliance fix and access_token_response hook to OAuth2Session. #63.
+- Added LinkedIn compliance fix.
+- Added refresh_token_response compliance hook, invoked before parsing the refresh token.
+- Correctly limit compliance hooks to running only once!
+- Content type guessing should only be done when no content type is given
+- OAuth1 now updates r.headers instead of replacing it with non case insensitive dict
+- Remove last use of Response.content (in OAuth1Session). #44.
+- State param can now be supplied in OAuth2Session.authorize_url
+
+
diff --git a/contrib/python/requests-oauthlib/.dist-info/top_level.txt b/contrib/python/requests-oauthlib/.dist-info/top_level.txt
new file mode 100644
index 0000000000..55d4f9073f
--- /dev/null
+++ b/contrib/python/requests-oauthlib/.dist-info/top_level.txt
@@ -0,0 +1 @@
+requests_oauthlib
diff --git a/contrib/python/requests-oauthlib/AUTHORS.rst b/contrib/python/requests-oauthlib/AUTHORS.rst
new file mode 100644
index 0000000000..c8fba5e997
--- /dev/null
+++ b/contrib/python/requests-oauthlib/AUTHORS.rst
@@ -0,0 +1,25 @@
+Requests-oauthlib is written and maintained by Kenneth Reitz and various
+contributors:
+
+Development Lead
+----------------
+
+- Kenneth Reitz <me@kennethreitz.com>
+
+Patches and Suggestions
+-----------------------
+
+- Cory Benfield <cory@lukasa.co.uk>
+- Ib Lundgren <ib.lundgren@gmail.com>
+- Devin Sevilla <dasevilla@gmail.com>
+- Imad Mouhtassem <mouhtasi@gmail.com>
+- Johan Euphrosine <proppy@google.com>
+- Johannes Spielmann <js@shezi.de>
+- Martin Trigaux <me@mart-e.be>
+- Matt McClure <matt.mcclure@mapmyfitness.com>
+- Mikhail Sobolev <mss@mawhrin.net>
+- Paul Bonser <misterpib@gmail.com>
+- Vinay Raikar <rockraikar@gmail.com>
+- kracekumar <me@kracekumar.com>
+- David Baumgold <david@davidbaumgold.com>
+- Craig Anderson <craiga@craiga.id.au>
diff --git a/contrib/python/requests-oauthlib/LICENSE b/contrib/python/requests-oauthlib/LICENSE
new file mode 100644
index 0000000000..de09f408ce
--- /dev/null
+++ b/contrib/python/requests-oauthlib/LICENSE
@@ -0,0 +1,15 @@
+ISC License
+
+Copyright (c) 2014 Kenneth Reitz.
+
+Permission to use, copy, modify, and/or distribute this software for any
+purpose with or without fee is hereby granted, provided that the above
+copyright notice and this permission notice appear in all copies.
+
+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
diff --git a/contrib/python/requests-oauthlib/README.rst b/contrib/python/requests-oauthlib/README.rst
new file mode 100644
index 0000000000..9fd1bb9767
--- /dev/null
+++ b/contrib/python/requests-oauthlib/README.rst
@@ -0,0 +1,58 @@
+Requests-OAuthlib |build-status| |coverage-status| |docs|
+=========================================================
+
+This project provides first-class OAuth library support for `Requests <http://python-requests.org>`_.
+
+The OAuth 1 workflow
+--------------------
+
+OAuth 1 can seem overly complicated and it sure has its quirks. Luckily,
+requests_oauthlib hides most of these and let you focus at the task at hand.
+
+Accessing protected resources using requests_oauthlib is as simple as:
+
+.. code-block:: pycon
+
+ >>> from requests_oauthlib import OAuth1Session
+ >>> twitter = OAuth1Session('client_key',
+ client_secret='client_secret',
+ resource_owner_key='resource_owner_key',
+ resource_owner_secret='resource_owner_secret')
+ >>> url = 'https://api.twitter.com/1/account/settings.json'
+ >>> r = twitter.get(url)
+
+Before accessing resources you will need to obtain a few credentials from your
+provider (e.g. Twitter) and authorization from the user for whom you wish to
+retrieve resources for. You can read all about this in the full
+`OAuth 1 workflow guide on RTD <https://requests-oauthlib.readthedocs.io/en/latest/oauth1_workflow.html>`_.
+
+The OAuth 2 workflow
+--------------------
+
+OAuth 2 is generally simpler than OAuth 1 but comes in more flavours. The most
+common being the Authorization Code Grant, also known as the WebApplication
+flow.
+
+Fetching a protected resource after obtaining an access token can be extremely
+simple. However, before accessing resources you will need to obtain a few
+credentials from your provider (e.g. Google) and authorization from the user
+for whom you wish to retrieve resources for. You can read all about this in the
+full `OAuth 2 workflow guide on RTD <https://requests-oauthlib.readthedocs.io/en/latest/oauth2_workflow.html>`_.
+
+Installation
+-------------
+
+To install requests and requests_oauthlib you can use pip:
+
+.. code-block:: bash
+
+ $ pip install requests requests_oauthlib
+
+.. |build-status| image:: https://github.com/requests/requests-oauthlib/actions/workflows/run-tests.yml/badge.svg
+ :target: https://github.com/requests/requests-oauthlib/actions
+.. |coverage-status| image:: https://img.shields.io/coveralls/requests/requests-oauthlib.svg
+ :target: https://coveralls.io/r/requests/requests-oauthlib
+.. |docs| image:: https://readthedocs.org/projects/requests-oauthlib/badge/
+ :alt: Documentation Status
+ :scale: 100%
+ :target: https://requests-oauthlib.readthedocs.io/
diff --git a/contrib/python/requests-oauthlib/requests_oauthlib/__init__.py b/contrib/python/requests-oauthlib/requests_oauthlib/__init__.py
new file mode 100644
index 0000000000..0d3e49f991
--- /dev/null
+++ b/contrib/python/requests-oauthlib/requests_oauthlib/__init__.py
@@ -0,0 +1,19 @@
+import logging
+
+from .oauth1_auth import OAuth1
+from .oauth1_session import OAuth1Session
+from .oauth2_auth import OAuth2
+from .oauth2_session import OAuth2Session, TokenUpdated
+
+__version__ = "1.3.1"
+
+import requests
+
+if requests.__version__ < "2.0.0":
+ msg = (
+ "You are using requests version %s, which is older than "
+ "requests-oauthlib expects, please upgrade to 2.0.0 or later."
+ )
+ raise Warning(msg % requests.__version__)
+
+logging.getLogger("requests_oauthlib").addHandler(logging.NullHandler())
diff --git a/contrib/python/requests-oauthlib/requests_oauthlib/compliance_fixes/__init__.py b/contrib/python/requests-oauthlib/requests_oauthlib/compliance_fixes/__init__.py
new file mode 100644
index 0000000000..0e8e3ac84f
--- /dev/null
+++ b/contrib/python/requests-oauthlib/requests_oauthlib/compliance_fixes/__init__.py
@@ -0,0 +1,10 @@
+from __future__ import absolute_import
+
+from .facebook import facebook_compliance_fix
+from .fitbit import fitbit_compliance_fix
+from .slack import slack_compliance_fix
+from .instagram import instagram_compliance_fix
+from .mailchimp import mailchimp_compliance_fix
+from .weibo import weibo_compliance_fix
+from .plentymarkets import plentymarkets_compliance_fix
+from .ebay import ebay_compliance_fix
diff --git a/contrib/python/requests-oauthlib/requests_oauthlib/compliance_fixes/douban.py b/contrib/python/requests-oauthlib/requests_oauthlib/compliance_fixes/douban.py
new file mode 100644
index 0000000000..ecc57b0818
--- /dev/null
+++ b/contrib/python/requests-oauthlib/requests_oauthlib/compliance_fixes/douban.py
@@ -0,0 +1,17 @@
+import json
+
+from oauthlib.common import to_unicode
+
+
+def douban_compliance_fix(session):
+ def fix_token_type(r):
+ token = json.loads(r.text)
+ token.setdefault("token_type", "Bearer")
+ fixed_token = json.dumps(token)
+ r._content = to_unicode(fixed_token).encode("utf-8")
+ return r
+
+ session._client_default_token_placement = "query"
+ session.register_compliance_hook("access_token_response", fix_token_type)
+
+ return session
diff --git a/contrib/python/requests-oauthlib/requests_oauthlib/compliance_fixes/ebay.py b/contrib/python/requests-oauthlib/requests_oauthlib/compliance_fixes/ebay.py
new file mode 100644
index 0000000000..4aa423b3fe
--- /dev/null
+++ b/contrib/python/requests-oauthlib/requests_oauthlib/compliance_fixes/ebay.py
@@ -0,0 +1,23 @@
+import json
+from oauthlib.common import to_unicode
+
+
+def ebay_compliance_fix(session):
+ def _compliance_fix(response):
+ token = json.loads(response.text)
+
+ # eBay responds with non-compliant token types.
+ # https://developer.ebay.com/api-docs/static/oauth-client-credentials-grant.html
+ # https://developer.ebay.com/api-docs/static/oauth-auth-code-grant-request.html
+ # Modify these to be "Bearer".
+ if token.get("token_type") in ["Application Access Token", "User Access Token"]:
+ token["token_type"] = "Bearer"
+ fixed_token = json.dumps(token)
+ response._content = to_unicode(fixed_token).encode("utf-8")
+
+ return response
+
+ session.register_compliance_hook("access_token_response", _compliance_fix)
+ session.register_compliance_hook("refresh_token_response", _compliance_fix)
+
+ return session
diff --git a/contrib/python/requests-oauthlib/requests_oauthlib/compliance_fixes/facebook.py b/contrib/python/requests-oauthlib/requests_oauthlib/compliance_fixes/facebook.py
new file mode 100644
index 0000000000..90e7921272
--- /dev/null
+++ b/contrib/python/requests-oauthlib/requests_oauthlib/compliance_fixes/facebook.py
@@ -0,0 +1,33 @@
+from json import dumps
+
+try:
+ from urlparse import parse_qsl
+except ImportError:
+ from urllib.parse import parse_qsl
+
+from oauthlib.common import to_unicode
+
+
+def facebook_compliance_fix(session):
+ def _compliance_fix(r):
+ # if Facebook claims to be sending us json, let's trust them.
+ if "application/json" in r.headers.get("content-type", {}):
+ return r
+
+ # Facebook returns a content-type of text/plain when sending their
+ # x-www-form-urlencoded responses, along with a 200. If not, let's
+ # assume we're getting JSON and bail on the fix.
+ if "text/plain" in r.headers.get("content-type", {}) and r.status_code == 200:
+ token = dict(parse_qsl(r.text, keep_blank_values=True))
+ else:
+ return r
+
+ expires = token.get("expires")
+ if expires is not None:
+ token["expires_in"] = expires
+ token["token_type"] = "Bearer"
+ r._content = to_unicode(dumps(token)).encode("UTF-8")
+ return r
+
+ session.register_compliance_hook("access_token_response", _compliance_fix)
+ return session
diff --git a/contrib/python/requests-oauthlib/requests_oauthlib/compliance_fixes/fitbit.py b/contrib/python/requests-oauthlib/requests_oauthlib/compliance_fixes/fitbit.py
new file mode 100644
index 0000000000..7e62702401
--- /dev/null
+++ b/contrib/python/requests-oauthlib/requests_oauthlib/compliance_fixes/fitbit.py
@@ -0,0 +1,25 @@
+"""
+The Fitbit API breaks from the OAuth2 RFC standard by returning an "errors"
+object list, rather than a single "error" string. This puts hooks in place so
+that oauthlib can process an error in the results from access token and refresh
+token responses. This is necessary to prevent getting the generic red herring
+MissingTokenError.
+"""
+
+from json import loads, dumps
+
+from oauthlib.common import to_unicode
+
+
+def fitbit_compliance_fix(session):
+ def _missing_error(r):
+ token = loads(r.text)
+ if "errors" in token:
+ # Set the error to the first one we have
+ token["error"] = token["errors"][0]["errorType"]
+ r._content = to_unicode(dumps(token)).encode("UTF-8")
+ return r
+
+ session.register_compliance_hook("access_token_response", _missing_error)
+ session.register_compliance_hook("refresh_token_response", _missing_error)
+ return session
diff --git a/contrib/python/requests-oauthlib/requests_oauthlib/compliance_fixes/instagram.py b/contrib/python/requests-oauthlib/requests_oauthlib/compliance_fixes/instagram.py
new file mode 100644
index 0000000000..4e07fe08b5
--- /dev/null
+++ b/contrib/python/requests-oauthlib/requests_oauthlib/compliance_fixes/instagram.py
@@ -0,0 +1,26 @@
+try:
+ from urlparse import urlparse, parse_qs
+except ImportError:
+ from urllib.parse import urlparse, parse_qs
+
+from oauthlib.common import add_params_to_uri
+
+
+def instagram_compliance_fix(session):
+ def _non_compliant_param_name(url, headers, data):
+ # If the user has already specified the token in the URL
+ # then there's nothing to do.
+ # If the specified token is different from ``session.access_token``,
+ # we assume the user intends to override the access token.
+ url_query = dict(parse_qs(urlparse(url).query))
+ token = url_query.get("access_token")
+ if token:
+ # Nothing to do, just return.
+ return url, headers, data
+
+ token = [("access_token", session.access_token)]
+ url = add_params_to_uri(url, token)
+ return url, headers, data
+
+ session.register_compliance_hook("protected_request", _non_compliant_param_name)
+ return session
diff --git a/contrib/python/requests-oauthlib/requests_oauthlib/compliance_fixes/mailchimp.py b/contrib/python/requests-oauthlib/requests_oauthlib/compliance_fixes/mailchimp.py
new file mode 100644
index 0000000000..c69ce9fdae
--- /dev/null
+++ b/contrib/python/requests-oauthlib/requests_oauthlib/compliance_fixes/mailchimp.py
@@ -0,0 +1,23 @@
+import json
+
+from oauthlib.common import to_unicode
+
+
+def mailchimp_compliance_fix(session):
+ def _null_scope(r):
+ token = json.loads(r.text)
+ if "scope" in token and token["scope"] is None:
+ token.pop("scope")
+ r._content = to_unicode(json.dumps(token)).encode("utf-8")
+ return r
+
+ def _non_zero_expiration(r):
+ token = json.loads(r.text)
+ if "expires_in" in token and token["expires_in"] == 0:
+ token["expires_in"] = 3600
+ r._content = to_unicode(json.dumps(token)).encode("utf-8")
+ return r
+
+ session.register_compliance_hook("access_token_response", _null_scope)
+ session.register_compliance_hook("access_token_response", _non_zero_expiration)
+ return session
diff --git a/contrib/python/requests-oauthlib/requests_oauthlib/compliance_fixes/plentymarkets.py b/contrib/python/requests-oauthlib/requests_oauthlib/compliance_fixes/plentymarkets.py
new file mode 100644
index 0000000000..9f605f058c
--- /dev/null
+++ b/contrib/python/requests-oauthlib/requests_oauthlib/compliance_fixes/plentymarkets.py
@@ -0,0 +1,29 @@
+from json import dumps, loads
+import re
+
+from oauthlib.common import to_unicode
+
+
+def plentymarkets_compliance_fix(session):
+ def _to_snake_case(n):
+ return re.sub("(.)([A-Z][a-z]+)", r"\1_\2", n).lower()
+
+ def _compliance_fix(r):
+ # Plenty returns the Token in CamelCase instead of _
+ if (
+ "application/json" in r.headers.get("content-type", {})
+ and r.status_code == 200
+ ):
+ token = loads(r.text)
+ else:
+ return r
+
+ fixed_token = {}
+ for k, v in token.items():
+ fixed_token[_to_snake_case(k)] = v
+
+ r._content = to_unicode(dumps(fixed_token)).encode("UTF-8")
+ return r
+
+ session.register_compliance_hook("access_token_response", _compliance_fix)
+ return session
diff --git a/contrib/python/requests-oauthlib/requests_oauthlib/compliance_fixes/slack.py b/contrib/python/requests-oauthlib/requests_oauthlib/compliance_fixes/slack.py
new file mode 100644
index 0000000000..3f574b03ad
--- /dev/null
+++ b/contrib/python/requests-oauthlib/requests_oauthlib/compliance_fixes/slack.py
@@ -0,0 +1,37 @@
+try:
+ from urlparse import urlparse, parse_qs
+except ImportError:
+ from urllib.parse import urlparse, parse_qs
+
+from oauthlib.common import add_params_to_uri
+
+
+def slack_compliance_fix(session):
+ def _non_compliant_param_name(url, headers, data):
+ # If the user has already specified the token, either in the URL
+ # or in a data dictionary, then there's nothing to do.
+ # If the specified token is different from ``session.access_token``,
+ # we assume the user intends to override the access token.
+ url_query = dict(parse_qs(urlparse(url).query))
+ token = url_query.get("token")
+ if not token and isinstance(data, dict):
+ token = data.get("token")
+
+ if token:
+ # Nothing to do, just return.
+ return url, headers, data
+
+ if not data:
+ data = {"token": session.access_token}
+ elif isinstance(data, dict):
+ data["token"] = session.access_token
+ else:
+ # ``data`` is something other than a dict: maybe a stream,
+ # maybe a file object, maybe something else. We can't easily
+ # modify it, so we'll set the token by modifying the URL instead.
+ token = [("token", session.access_token)]
+ url = add_params_to_uri(url, token)
+ return url, headers, data
+
+ session.register_compliance_hook("protected_request", _non_compliant_param_name)
+ return session
diff --git a/contrib/python/requests-oauthlib/requests_oauthlib/compliance_fixes/weibo.py b/contrib/python/requests-oauthlib/requests_oauthlib/compliance_fixes/weibo.py
new file mode 100644
index 0000000000..6733abeb15
--- /dev/null
+++ b/contrib/python/requests-oauthlib/requests_oauthlib/compliance_fixes/weibo.py
@@ -0,0 +1,15 @@
+from json import loads, dumps
+
+from oauthlib.common import to_unicode
+
+
+def weibo_compliance_fix(session):
+ def _missing_token_type(r):
+ token = loads(r.text)
+ token["token_type"] = "Bearer"
+ r._content = to_unicode(dumps(token)).encode("UTF-8")
+ return r
+
+ session._client.default_token_placement = "query"
+ session.register_compliance_hook("access_token_response", _missing_token_type)
+ return session
diff --git a/contrib/python/requests-oauthlib/requests_oauthlib/oauth1_auth.py b/contrib/python/requests-oauthlib/requests_oauthlib/oauth1_auth.py
new file mode 100644
index 0000000000..cfbbd5902c
--- /dev/null
+++ b/contrib/python/requests-oauthlib/requests_oauthlib/oauth1_auth.py
@@ -0,0 +1,117 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+import logging
+
+from oauthlib.common import extract_params
+from oauthlib.oauth1 import Client, SIGNATURE_HMAC, SIGNATURE_TYPE_AUTH_HEADER
+from oauthlib.oauth1 import SIGNATURE_TYPE_BODY
+from requests.compat import is_py3
+from requests.utils import to_native_string
+from requests.auth import AuthBase
+
+CONTENT_TYPE_FORM_URLENCODED = "application/x-www-form-urlencoded"
+CONTENT_TYPE_MULTI_PART = "multipart/form-data"
+
+if is_py3:
+ unicode = str
+
+log = logging.getLogger(__name__)
+
+# OBS!: Correct signing of requests are conditional on invoking OAuth1
+# as the last step of preparing a request, or at least having the
+# content-type set properly.
+class OAuth1(AuthBase):
+ """Signs the request using OAuth 1 (RFC5849)"""
+
+ client_class = Client
+
+ def __init__(
+ self,
+ client_key,
+ client_secret=None,
+ resource_owner_key=None,
+ resource_owner_secret=None,
+ callback_uri=None,
+ signature_method=SIGNATURE_HMAC,
+ signature_type=SIGNATURE_TYPE_AUTH_HEADER,
+ rsa_key=None,
+ verifier=None,
+ decoding="utf-8",
+ client_class=None,
+ force_include_body=False,
+ **kwargs
+ ):
+
+ try:
+ signature_type = signature_type.upper()
+ except AttributeError:
+ pass
+
+ client_class = client_class or self.client_class
+
+ self.force_include_body = force_include_body
+
+ self.client = client_class(
+ client_key,
+ client_secret,
+ resource_owner_key,
+ resource_owner_secret,
+ callback_uri,
+ signature_method,
+ signature_type,
+ rsa_key,
+ verifier,
+ decoding=decoding,
+ **kwargs
+ )
+
+ def __call__(self, r):
+ """Add OAuth parameters to the request.
+
+ Parameters may be included from the body if the content-type is
+ urlencoded, if no content type is set a guess is made.
+ """
+ # Overwriting url is safe here as request will not modify it past
+ # this point.
+ log.debug("Signing request %s using client %s", r, self.client)
+
+ content_type = r.headers.get("Content-Type", "")
+ if (
+ not content_type
+ and extract_params(r.body)
+ or self.client.signature_type == SIGNATURE_TYPE_BODY
+ ):
+ content_type = CONTENT_TYPE_FORM_URLENCODED
+ if not isinstance(content_type, unicode):
+ content_type = content_type.decode("utf-8")
+
+ is_form_encoded = CONTENT_TYPE_FORM_URLENCODED in content_type
+
+ log.debug(
+ "Including body in call to sign: %s",
+ is_form_encoded or self.force_include_body,
+ )
+
+ if is_form_encoded:
+ r.headers["Content-Type"] = CONTENT_TYPE_FORM_URLENCODED
+ r.url, headers, r.body = self.client.sign(
+ unicode(r.url), unicode(r.method), r.body or "", r.headers
+ )
+ elif self.force_include_body:
+ # To allow custom clients to work on non form encoded bodies.
+ r.url, headers, r.body = self.client.sign(
+ unicode(r.url), unicode(r.method), r.body or "", r.headers
+ )
+ else:
+ # Omit body data in the signing of non form-encoded requests
+ r.url, headers, _ = self.client.sign(
+ unicode(r.url), unicode(r.method), None, r.headers
+ )
+
+ r.prepare_headers(headers)
+ r.url = to_native_string(r.url)
+ log.debug("Updated url: %s", r.url)
+ log.debug("Updated headers: %s", headers)
+ log.debug("Updated body: %r", r.body)
+ return r
diff --git a/contrib/python/requests-oauthlib/requests_oauthlib/oauth1_session.py b/contrib/python/requests-oauthlib/requests_oauthlib/oauth1_session.py
new file mode 100644
index 0000000000..88f2853ca0
--- /dev/null
+++ b/contrib/python/requests-oauthlib/requests_oauthlib/oauth1_session.py
@@ -0,0 +1,400 @@
+from __future__ import unicode_literals
+
+try:
+ from urlparse import urlparse
+except ImportError:
+ from urllib.parse import urlparse
+
+import logging
+
+from oauthlib.common import add_params_to_uri
+from oauthlib.common import urldecode as _urldecode
+from oauthlib.oauth1 import SIGNATURE_HMAC, SIGNATURE_RSA, SIGNATURE_TYPE_AUTH_HEADER
+import requests
+
+from . import OAuth1
+
+
+log = logging.getLogger(__name__)
+
+
+def urldecode(body):
+ """Parse query or json to python dictionary"""
+ try:
+ return _urldecode(body)
+ except Exception:
+ import json
+
+ return json.loads(body)
+
+
+class TokenRequestDenied(ValueError):
+ def __init__(self, message, response):
+ super(TokenRequestDenied, self).__init__(message)
+ self.response = response
+
+ @property
+ def status_code(self):
+ """For backwards-compatibility purposes"""
+ return self.response.status_code
+
+
+class TokenMissing(ValueError):
+ def __init__(self, message, response):
+ super(TokenMissing, self).__init__(message)
+ self.response = response
+
+
+class VerifierMissing(ValueError):
+ pass
+
+
+class OAuth1Session(requests.Session):
+ """Request signing and convenience methods for the oauth dance.
+
+ What is the difference between OAuth1Session and OAuth1?
+
+ OAuth1Session actually uses OAuth1 internally and its purpose is to assist
+ in the OAuth workflow through convenience methods to prepare authorization
+ URLs and parse the various token and redirection responses. It also provide
+ rudimentary validation of responses.
+
+ An example of the OAuth workflow using a basic CLI app and Twitter.
+
+ >>> # Credentials obtained during the registration.
+ >>> client_key = 'client key'
+ >>> client_secret = 'secret'
+ >>> callback_uri = 'https://127.0.0.1/callback'
+ >>>
+ >>> # Endpoints found in the OAuth provider API documentation
+ >>> request_token_url = 'https://api.twitter.com/oauth/request_token'
+ >>> authorization_url = 'https://api.twitter.com/oauth/authorize'
+ >>> access_token_url = 'https://api.twitter.com/oauth/access_token'
+ >>>
+ >>> oauth_session = OAuth1Session(client_key,client_secret=client_secret, callback_uri=callback_uri)
+ >>>
+ >>> # First step, fetch the request token.
+ >>> oauth_session.fetch_request_token(request_token_url)
+ {
+ 'oauth_token': 'kjerht2309u',
+ 'oauth_token_secret': 'lsdajfh923874',
+ }
+ >>>
+ >>> # Second step. Follow this link and authorize
+ >>> oauth_session.authorization_url(authorization_url)
+ 'https://api.twitter.com/oauth/authorize?oauth_token=sdf0o9823sjdfsdf&oauth_callback=https%3A%2F%2F127.0.0.1%2Fcallback'
+ >>>
+ >>> # Third step. Fetch the access token
+ >>> redirect_response = raw_input('Paste the full redirect URL here.')
+ >>> oauth_session.parse_authorization_response(redirect_response)
+ {
+ 'oauth_token: 'kjerht2309u',
+ 'oauth_token_secret: 'lsdajfh923874',
+ 'oauth_verifier: 'w34o8967345',
+ }
+ >>> oauth_session.fetch_access_token(access_token_url)
+ {
+ 'oauth_token': 'sdf0o9823sjdfsdf',
+ 'oauth_token_secret': '2kjshdfp92i34asdasd',
+ }
+ >>> # Done. You can now make OAuth requests.
+ >>> status_url = 'http://api.twitter.com/1/statuses/update.json'
+ >>> new_status = {'status': 'hello world!'}
+ >>> oauth_session.post(status_url, data=new_status)
+ <Response [200]>
+ """
+
+ def __init__(
+ self,
+ client_key,
+ client_secret=None,
+ resource_owner_key=None,
+ resource_owner_secret=None,
+ callback_uri=None,
+ signature_method=SIGNATURE_HMAC,
+ signature_type=SIGNATURE_TYPE_AUTH_HEADER,
+ rsa_key=None,
+ verifier=None,
+ client_class=None,
+ force_include_body=False,
+ **kwargs
+ ):
+ """Construct the OAuth 1 session.
+
+ :param client_key: A client specific identifier.
+ :param client_secret: A client specific secret used to create HMAC and
+ plaintext signatures.
+ :param resource_owner_key: A resource owner key, also referred to as
+ request token or access token depending on
+ when in the workflow it is used.
+ :param resource_owner_secret: A resource owner secret obtained with
+ either a request or access token. Often
+ referred to as token secret.
+ :param callback_uri: The URL the user is redirect back to after
+ authorization.
+ :param signature_method: Signature methods determine how the OAuth
+ signature is created. The three options are
+ oauthlib.oauth1.SIGNATURE_HMAC (default),
+ oauthlib.oauth1.SIGNATURE_RSA and
+ oauthlib.oauth1.SIGNATURE_PLAIN.
+ :param signature_type: Signature type decides where the OAuth
+ parameters are added. Either in the
+ Authorization header (default) or to the URL
+ query parameters or the request body. Defined as
+ oauthlib.oauth1.SIGNATURE_TYPE_AUTH_HEADER,
+ oauthlib.oauth1.SIGNATURE_TYPE_QUERY and
+ oauthlib.oauth1.SIGNATURE_TYPE_BODY
+ respectively.
+ :param rsa_key: The private RSA key as a string. Can only be used with
+ signature_method=oauthlib.oauth1.SIGNATURE_RSA.
+ :param verifier: A verifier string to prove authorization was granted.
+ :param client_class: A subclass of `oauthlib.oauth1.Client` to use with
+ `requests_oauthlib.OAuth1` instead of the default
+ :param force_include_body: Always include the request body in the
+ signature creation.
+ :param **kwargs: Additional keyword arguments passed to `OAuth1`
+ """
+ super(OAuth1Session, self).__init__()
+ self._client = OAuth1(
+ client_key,
+ client_secret=client_secret,
+ resource_owner_key=resource_owner_key,
+ resource_owner_secret=resource_owner_secret,
+ callback_uri=callback_uri,
+ signature_method=signature_method,
+ signature_type=signature_type,
+ rsa_key=rsa_key,
+ verifier=verifier,
+ client_class=client_class,
+ force_include_body=force_include_body,
+ **kwargs
+ )
+ self.auth = self._client
+
+ @property
+ def token(self):
+ oauth_token = self._client.client.resource_owner_key
+ oauth_token_secret = self._client.client.resource_owner_secret
+ oauth_verifier = self._client.client.verifier
+
+ token_dict = {}
+ if oauth_token:
+ token_dict["oauth_token"] = oauth_token
+ if oauth_token_secret:
+ token_dict["oauth_token_secret"] = oauth_token_secret
+ if oauth_verifier:
+ token_dict["oauth_verifier"] = oauth_verifier
+
+ return token_dict
+
+ @token.setter
+ def token(self, value):
+ self._populate_attributes(value)
+
+ @property
+ def authorized(self):
+ """Boolean that indicates whether this session has an OAuth token
+ or not. If `self.authorized` is True, you can reasonably expect
+ OAuth-protected requests to the resource to succeed. If
+ `self.authorized` is False, you need the user to go through the OAuth
+ authentication dance before OAuth-protected requests to the resource
+ will succeed.
+ """
+ if self._client.client.signature_method == SIGNATURE_RSA:
+ # RSA only uses resource_owner_key
+ return bool(self._client.client.resource_owner_key)
+ else:
+ # other methods of authentication use all three pieces
+ return (
+ bool(self._client.client.client_secret)
+ and bool(self._client.client.resource_owner_key)
+ and bool(self._client.client.resource_owner_secret)
+ )
+
+ def authorization_url(self, url, request_token=None, **kwargs):
+ """Create an authorization URL by appending request_token and optional
+ kwargs to url.
+
+ This is the second step in the OAuth 1 workflow. The user should be
+ redirected to this authorization URL, grant access to you, and then
+ be redirected back to you. The redirection back can either be specified
+ during client registration or by supplying a callback URI per request.
+
+ :param url: The authorization endpoint URL.
+ :param request_token: The previously obtained request token.
+ :param kwargs: Optional parameters to append to the URL.
+ :returns: The authorization URL with new parameters embedded.
+
+ An example using a registered default callback URI.
+
+ >>> request_token_url = 'https://api.twitter.com/oauth/request_token'
+ >>> authorization_url = 'https://api.twitter.com/oauth/authorize'
+ >>> oauth_session = OAuth1Session('client-key', client_secret='secret')
+ >>> oauth_session.fetch_request_token(request_token_url)
+ {
+ 'oauth_token': 'sdf0o9823sjdfsdf',
+ 'oauth_token_secret': '2kjshdfp92i34asdasd',
+ }
+ >>> oauth_session.authorization_url(authorization_url)
+ 'https://api.twitter.com/oauth/authorize?oauth_token=sdf0o9823sjdfsdf'
+ >>> oauth_session.authorization_url(authorization_url, foo='bar')
+ 'https://api.twitter.com/oauth/authorize?oauth_token=sdf0o9823sjdfsdf&foo=bar'
+
+ An example using an explicit callback URI.
+
+ >>> request_token_url = 'https://api.twitter.com/oauth/request_token'
+ >>> authorization_url = 'https://api.twitter.com/oauth/authorize'
+ >>> oauth_session = OAuth1Session('client-key', client_secret='secret', callback_uri='https://127.0.0.1/callback')
+ >>> oauth_session.fetch_request_token(request_token_url)
+ {
+ 'oauth_token': 'sdf0o9823sjdfsdf',
+ 'oauth_token_secret': '2kjshdfp92i34asdasd',
+ }
+ >>> oauth_session.authorization_url(authorization_url)
+ 'https://api.twitter.com/oauth/authorize?oauth_token=sdf0o9823sjdfsdf&oauth_callback=https%3A%2F%2F127.0.0.1%2Fcallback'
+ """
+ kwargs["oauth_token"] = request_token or self._client.client.resource_owner_key
+ log.debug("Adding parameters %s to url %s", kwargs, url)
+ return add_params_to_uri(url, kwargs.items())
+
+ def fetch_request_token(self, url, realm=None, **request_kwargs):
+ r"""Fetch a request token.
+
+ This is the first step in the OAuth 1 workflow. A request token is
+ obtained by making a signed post request to url. The token is then
+ parsed from the application/x-www-form-urlencoded response and ready
+ to be used to construct an authorization url.
+
+ :param url: The request token endpoint URL.
+ :param realm: A list of realms to request access to.
+ :param \*\*request_kwargs: Optional arguments passed to ''post''
+ function in ''requests.Session''
+ :returns: The response in dict format.
+
+ Note that a previously set callback_uri will be reset for your
+ convenience, or else signature creation will be incorrect on
+ consecutive requests.
+
+ >>> request_token_url = 'https://api.twitter.com/oauth/request_token'
+ >>> oauth_session = OAuth1Session('client-key', client_secret='secret')
+ >>> oauth_session.fetch_request_token(request_token_url)
+ {
+ 'oauth_token': 'sdf0o9823sjdfsdf',
+ 'oauth_token_secret': '2kjshdfp92i34asdasd',
+ }
+ """
+ self._client.client.realm = " ".join(realm) if realm else None
+ token = self._fetch_token(url, **request_kwargs)
+ log.debug("Resetting callback_uri and realm (not needed in next phase).")
+ self._client.client.callback_uri = None
+ self._client.client.realm = None
+ return token
+
+ def fetch_access_token(self, url, verifier=None, **request_kwargs):
+ """Fetch an access token.
+
+ This is the final step in the OAuth 1 workflow. An access token is
+ obtained using all previously obtained credentials, including the
+ verifier from the authorization step.
+
+ Note that a previously set verifier will be reset for your
+ convenience, or else signature creation will be incorrect on
+ consecutive requests.
+
+ >>> access_token_url = 'https://api.twitter.com/oauth/access_token'
+ >>> redirect_response = 'https://127.0.0.1/callback?oauth_token=kjerht2309uf&oauth_token_secret=lsdajfh923874&oauth_verifier=w34o8967345'
+ >>> oauth_session = OAuth1Session('client-key', client_secret='secret')
+ >>> oauth_session.parse_authorization_response(redirect_response)
+ {
+ 'oauth_token: 'kjerht2309u',
+ 'oauth_token_secret: 'lsdajfh923874',
+ 'oauth_verifier: 'w34o8967345',
+ }
+ >>> oauth_session.fetch_access_token(access_token_url)
+ {
+ 'oauth_token': 'sdf0o9823sjdfsdf',
+ 'oauth_token_secret': '2kjshdfp92i34asdasd',
+ }
+ """
+ if verifier:
+ self._client.client.verifier = verifier
+ if not getattr(self._client.client, "verifier", None):
+ raise VerifierMissing("No client verifier has been set.")
+ token = self._fetch_token(url, **request_kwargs)
+ log.debug("Resetting verifier attribute, should not be used anymore.")
+ self._client.client.verifier = None
+ return token
+
+ def parse_authorization_response(self, url):
+ """Extract parameters from the post authorization redirect response URL.
+
+ :param url: The full URL that resulted from the user being redirected
+ back from the OAuth provider to you, the client.
+ :returns: A dict of parameters extracted from the URL.
+
+ >>> redirect_response = 'https://127.0.0.1/callback?oauth_token=kjerht2309uf&oauth_token_secret=lsdajfh923874&oauth_verifier=w34o8967345'
+ >>> oauth_session = OAuth1Session('client-key', client_secret='secret')
+ >>> oauth_session.parse_authorization_response(redirect_response)
+ {
+ 'oauth_token: 'kjerht2309u',
+ 'oauth_token_secret: 'lsdajfh923874',
+ 'oauth_verifier: 'w34o8967345',
+ }
+ """
+ log.debug("Parsing token from query part of url %s", url)
+ token = dict(urldecode(urlparse(url).query))
+ log.debug("Updating internal client token attribute.")
+ self._populate_attributes(token)
+ self.token = token
+ return token
+
+ def _populate_attributes(self, token):
+ if "oauth_token" in token:
+ self._client.client.resource_owner_key = token["oauth_token"]
+ else:
+ raise TokenMissing(
+ "Response does not contain a token: {resp}".format(resp=token), token
+ )
+ if "oauth_token_secret" in token:
+ self._client.client.resource_owner_secret = token["oauth_token_secret"]
+ if "oauth_verifier" in token:
+ self._client.client.verifier = token["oauth_verifier"]
+
+ def _fetch_token(self, url, **request_kwargs):
+ log.debug("Fetching token from %s using client %s", url, self._client.client)
+ r = self.post(url, **request_kwargs)
+
+ if r.status_code >= 400:
+ error = "Token request failed with code %s, response was '%s'."
+ raise TokenRequestDenied(error % (r.status_code, r.text), r)
+
+ log.debug('Decoding token from response "%s"', r.text)
+ try:
+ token = dict(urldecode(r.text.strip()))
+ except ValueError as e:
+ error = (
+ "Unable to decode token from token response. "
+ "This is commonly caused by an unsuccessful request where"
+ " a non urlencoded error message is returned. "
+ "The decoding error was %s"
+ "" % e
+ )
+ raise ValueError(error)
+
+ log.debug("Obtained token %s", token)
+ log.debug("Updating internal client attributes from token data.")
+ self._populate_attributes(token)
+ self.token = token
+ return token
+
+ def rebuild_auth(self, prepared_request, response):
+ """
+ When being redirected we should always strip Authorization
+ header, since nonce may not be reused as per OAuth spec.
+ """
+ if "Authorization" in prepared_request.headers:
+ # If we get redirected to a new host, we should strip out
+ # any authentication headers.
+ prepared_request.headers.pop("Authorization", True)
+ prepared_request.prepare_auth(self.auth)
+ return
diff --git a/contrib/python/requests-oauthlib/requests_oauthlib/oauth2_auth.py b/contrib/python/requests-oauthlib/requests_oauthlib/oauth2_auth.py
new file mode 100644
index 0000000000..b880f72f58
--- /dev/null
+++ b/contrib/python/requests-oauthlib/requests_oauthlib/oauth2_auth.py
@@ -0,0 +1,37 @@
+from __future__ import unicode_literals
+from oauthlib.oauth2 import WebApplicationClient, InsecureTransportError
+from oauthlib.oauth2 import is_secure_transport
+from requests.auth import AuthBase
+
+
+class OAuth2(AuthBase):
+ """Adds proof of authorization (OAuth2 token) to the request."""
+
+ def __init__(self, client_id=None, client=None, token=None):
+ """Construct a new OAuth 2 authorization object.
+
+ :param client_id: Client id obtained during registration
+ :param client: :class:`oauthlib.oauth2.Client` to be used. Default is
+ WebApplicationClient which is useful for any
+ hosted application but not mobile or desktop.
+ :param token: Token dictionary, must include access_token
+ and token_type.
+ """
+ self._client = client or WebApplicationClient(client_id, token=token)
+ if token:
+ for k, v in token.items():
+ setattr(self._client, k, v)
+
+ def __call__(self, r):
+ """Append an OAuth 2 token to the request.
+
+ Note that currently HTTPS is required for all requests. There may be
+ a token type that allows for plain HTTP in the future and then this
+ should be updated to allow plain HTTP on a white list basis.
+ """
+ if not is_secure_transport(r.url):
+ raise InsecureTransportError()
+ r.url, r.headers, r.body = self._client.add_token(
+ r.url, http_method=r.method, body=r.body, headers=r.headers
+ )
+ return r
diff --git a/contrib/python/requests-oauthlib/requests_oauthlib/oauth2_session.py b/contrib/python/requests-oauthlib/requests_oauthlib/oauth2_session.py
new file mode 100644
index 0000000000..db4468089b
--- /dev/null
+++ b/contrib/python/requests-oauthlib/requests_oauthlib/oauth2_session.py
@@ -0,0 +1,540 @@
+from __future__ import unicode_literals
+
+import logging
+
+from oauthlib.common import generate_token, urldecode
+from oauthlib.oauth2 import WebApplicationClient, InsecureTransportError
+from oauthlib.oauth2 import LegacyApplicationClient
+from oauthlib.oauth2 import TokenExpiredError, is_secure_transport
+import requests
+
+log = logging.getLogger(__name__)
+
+
+class TokenUpdated(Warning):
+ def __init__(self, token):
+ super(TokenUpdated, self).__init__()
+ self.token = token
+
+
+class OAuth2Session(requests.Session):
+ """Versatile OAuth 2 extension to :class:`requests.Session`.
+
+ Supports any grant type adhering to :class:`oauthlib.oauth2.Client` spec
+ including the four core OAuth 2 grants.
+
+ Can be used to create authorization urls, fetch tokens and access protected
+ resources using the :class:`requests.Session` interface you are used to.
+
+ - :class:`oauthlib.oauth2.WebApplicationClient` (default): Authorization Code Grant
+ - :class:`oauthlib.oauth2.MobileApplicationClient`: Implicit Grant
+ - :class:`oauthlib.oauth2.LegacyApplicationClient`: Password Credentials Grant
+ - :class:`oauthlib.oauth2.BackendApplicationClient`: Client Credentials Grant
+
+ Note that the only time you will be using Implicit Grant from python is if
+ you are driving a user agent able to obtain URL fragments.
+ """
+
+ def __init__(
+ self,
+ client_id=None,
+ client=None,
+ auto_refresh_url=None,
+ auto_refresh_kwargs=None,
+ scope=None,
+ redirect_uri=None,
+ token=None,
+ state=None,
+ token_updater=None,
+ **kwargs
+ ):
+ """Construct a new OAuth 2 client session.
+
+ :param client_id: Client id obtained during registration
+ :param client: :class:`oauthlib.oauth2.Client` to be used. Default is
+ WebApplicationClient which is useful for any
+ hosted application but not mobile or desktop.
+ :param scope: List of scopes you wish to request access to
+ :param redirect_uri: Redirect URI you registered as callback
+ :param token: Token dictionary, must include access_token
+ and token_type.
+ :param state: State string used to prevent CSRF. This will be given
+ when creating the authorization url and must be supplied
+ when parsing the authorization response.
+ Can be either a string or a no argument callable.
+ :auto_refresh_url: Refresh token endpoint URL, must be HTTPS. Supply
+ this if you wish the client to automatically refresh
+ your access tokens.
+ :auto_refresh_kwargs: Extra arguments to pass to the refresh token
+ endpoint.
+ :token_updater: Method with one argument, token, to be used to update
+ your token database on automatic token refresh. If not
+ set a TokenUpdated warning will be raised when a token
+ has been refreshed. This warning will carry the token
+ in its token argument.
+ :param kwargs: Arguments to pass to the Session constructor.
+ """
+ super(OAuth2Session, self).__init__(**kwargs)
+ self._client = client or WebApplicationClient(client_id, token=token)
+ self.token = token or {}
+ self.scope = scope
+ self.redirect_uri = redirect_uri
+ self.state = state or generate_token
+ self._state = state
+ self.auto_refresh_url = auto_refresh_url
+ self.auto_refresh_kwargs = auto_refresh_kwargs or {}
+ self.token_updater = token_updater
+
+ # Ensure that requests doesn't do any automatic auth. See #278.
+ # The default behavior can be re-enabled by setting auth to None.
+ self.auth = lambda r: r
+
+ # Allow customizations for non compliant providers through various
+ # hooks to adjust requests and responses.
+ self.compliance_hook = {
+ "access_token_response": set(),
+ "refresh_token_response": set(),
+ "protected_request": set(),
+ }
+
+ def new_state(self):
+ """Generates a state string to be used in authorizations."""
+ try:
+ self._state = self.state()
+ log.debug("Generated new state %s.", self._state)
+ except TypeError:
+ self._state = self.state
+ log.debug("Re-using previously supplied state %s.", self._state)
+ return self._state
+
+ @property
+ def client_id(self):
+ return getattr(self._client, "client_id", None)
+
+ @client_id.setter
+ def client_id(self, value):
+ self._client.client_id = value
+
+ @client_id.deleter
+ def client_id(self):
+ del self._client.client_id
+
+ @property
+ def token(self):
+ return getattr(self._client, "token", None)
+
+ @token.setter
+ def token(self, value):
+ self._client.token = value
+ self._client.populate_token_attributes(value)
+
+ @property
+ def access_token(self):
+ return getattr(self._client, "access_token", None)
+
+ @access_token.setter
+ def access_token(self, value):
+ self._client.access_token = value
+
+ @access_token.deleter
+ def access_token(self):
+ del self._client.access_token
+
+ @property
+ def authorized(self):
+ """Boolean that indicates whether this session has an OAuth token
+ or not. If `self.authorized` is True, you can reasonably expect
+ OAuth-protected requests to the resource to succeed. If
+ `self.authorized` is False, you need the user to go through the OAuth
+ authentication dance before OAuth-protected requests to the resource
+ will succeed.
+ """
+ return bool(self.access_token)
+
+ def authorization_url(self, url, state=None, **kwargs):
+ """Form an authorization URL.
+
+ :param url: Authorization endpoint url, must be HTTPS.
+ :param state: An optional state string for CSRF protection. If not
+ given it will be generated for you.
+ :param kwargs: Extra parameters to include.
+ :return: authorization_url, state
+ """
+ state = state or self.new_state()
+ return (
+ self._client.prepare_request_uri(
+ url,
+ redirect_uri=self.redirect_uri,
+ scope=self.scope,
+ state=state,
+ **kwargs
+ ),
+ state,
+ )
+
+ def fetch_token(
+ self,
+ token_url,
+ code=None,
+ authorization_response=None,
+ body="",
+ auth=None,
+ username=None,
+ password=None,
+ method="POST",
+ force_querystring=False,
+ timeout=None,
+ headers=None,
+ verify=True,
+ proxies=None,
+ include_client_id=None,
+ client_secret=None,
+ cert=None,
+ **kwargs
+ ):
+ """Generic method for fetching an access token from the token endpoint.
+
+ If you are using the MobileApplicationClient you will want to use
+ `token_from_fragment` instead of `fetch_token`.
+
+ The current implementation enforces the RFC guidelines.
+
+ :param token_url: Token endpoint URL, must use HTTPS.
+ :param code: Authorization code (used by WebApplicationClients).
+ :param authorization_response: Authorization response URL, the callback
+ URL of the request back to you. Used by
+ WebApplicationClients instead of code.
+ :param body: Optional application/x-www-form-urlencoded body to add the
+ include in the token request. Prefer kwargs over body.
+ :param auth: An auth tuple or method as accepted by `requests`.
+ :param username: Username required by LegacyApplicationClients to appear
+ in the request body.
+ :param password: Password required by LegacyApplicationClients to appear
+ in the request body.
+ :param method: The HTTP method used to make the request. Defaults
+ to POST, but may also be GET. Other methods should
+ be added as needed.
+ :param force_querystring: If True, force the request body to be sent
+ in the querystring instead.
+ :param timeout: Timeout of the request in seconds.
+ :param headers: Dict to default request headers with.
+ :param verify: Verify SSL certificate.
+ :param proxies: The `proxies` argument is passed onto `requests`.
+ :param include_client_id: Should the request body include the
+ `client_id` parameter. Default is `None`,
+ which will attempt to autodetect. This can be
+ forced to always include (True) or never
+ include (False).
+ :param client_secret: The `client_secret` paired to the `client_id`.
+ This is generally required unless provided in the
+ `auth` tuple. If the value is `None`, it will be
+ omitted from the request, however if the value is
+ an empty string, an empty string will be sent.
+ :param cert: Client certificate to send for OAuth 2.0 Mutual-TLS Client
+ Authentication (draft-ietf-oauth-mtls). Can either be the
+ path of a file containing the private key and certificate or
+ a tuple of two filenames for certificate and key.
+ :param kwargs: Extra parameters to include in the token request.
+ :return: A token dict
+ """
+ if not is_secure_transport(token_url):
+ raise InsecureTransportError()
+
+ if not code and authorization_response:
+ self._client.parse_request_uri_response(
+ authorization_response, state=self._state
+ )
+ code = self._client.code
+ elif not code and isinstance(self._client, WebApplicationClient):
+ code = self._client.code
+ if not code:
+ raise ValueError(
+ "Please supply either code or " "authorization_response parameters."
+ )
+
+ # Earlier versions of this library build an HTTPBasicAuth header out of
+ # `username` and `password`. The RFC states, however these attributes
+ # must be in the request body and not the header.
+ # If an upstream server is not spec compliant and requires them to
+ # appear as an Authorization header, supply an explicit `auth` header
+ # to this function.
+ # This check will allow for empty strings, but not `None`.
+ #
+ # References
+ # 4.3.2 - Resource Owner Password Credentials Grant
+ # https://tools.ietf.org/html/rfc6749#section-4.3.2
+
+ if isinstance(self._client, LegacyApplicationClient):
+ if username is None:
+ raise ValueError(
+ "`LegacyApplicationClient` requires both the "
+ "`username` and `password` parameters."
+ )
+ if password is None:
+ raise ValueError(
+ "The required parameter `username` was supplied, "
+ "but `password` was not."
+ )
+
+ # merge username and password into kwargs for `prepare_request_body`
+ if username is not None:
+ kwargs["username"] = username
+ if password is not None:
+ kwargs["password"] = password
+
+ # is an auth explicitly supplied?
+ if auth is not None:
+ # if we're dealing with the default of `include_client_id` (None):
+ # we will assume the `auth` argument is for an RFC compliant server
+ # and we should not send the `client_id` in the body.
+ # This approach allows us to still force the client_id by submitting
+ # `include_client_id=True` along with an `auth` object.
+ if include_client_id is None:
+ include_client_id = False
+
+ # otherwise we may need to create an auth header
+ else:
+ # since we don't have an auth header, we MAY need to create one
+ # it is possible that we want to send the `client_id` in the body
+ # if so, `include_client_id` should be set to True
+ # otherwise, we will generate an auth header
+ if include_client_id is not True:
+ client_id = self.client_id
+ if client_id:
+ log.debug(
+ 'Encoding `client_id` "%s" with `client_secret` '
+ "as Basic auth credentials.",
+ client_id,
+ )
+ client_secret = client_secret if client_secret is not None else ""
+ auth = requests.auth.HTTPBasicAuth(client_id, client_secret)
+
+ if include_client_id:
+ # this was pulled out of the params
+ # it needs to be passed into prepare_request_body
+ if client_secret is not None:
+ kwargs["client_secret"] = client_secret
+
+ body = self._client.prepare_request_body(
+ code=code,
+ body=body,
+ redirect_uri=self.redirect_uri,
+ include_client_id=include_client_id,
+ **kwargs
+ )
+
+ headers = headers or {
+ "Accept": "application/json",
+ "Content-Type": "application/x-www-form-urlencoded;charset=UTF-8",
+ }
+ self.token = {}
+ request_kwargs = {}
+ if method.upper() == "POST":
+ request_kwargs["params" if force_querystring else "data"] = dict(
+ urldecode(body)
+ )
+ elif method.upper() == "GET":
+ request_kwargs["params"] = dict(urldecode(body))
+ else:
+ raise ValueError("The method kwarg must be POST or GET.")
+
+ r = self.request(
+ method=method,
+ url=token_url,
+ timeout=timeout,
+ headers=headers,
+ auth=auth,
+ verify=verify,
+ proxies=proxies,
+ cert=cert,
+ **request_kwargs
+ )
+
+ log.debug("Request to fetch token completed with status %s.", r.status_code)
+ log.debug("Request url was %s", r.request.url)
+ log.debug("Request headers were %s", r.request.headers)
+ log.debug("Request body was %s", r.request.body)
+ log.debug("Response headers were %s and content %s.", r.headers, r.text)
+ log.debug(
+ "Invoking %d token response hooks.",
+ len(self.compliance_hook["access_token_response"]),
+ )
+ for hook in self.compliance_hook["access_token_response"]:
+ log.debug("Invoking hook %s.", hook)
+ r = hook(r)
+
+ self._client.parse_request_body_response(r.text, scope=self.scope)
+ self.token = self._client.token
+ log.debug("Obtained token %s.", self.token)
+ return self.token
+
+ def token_from_fragment(self, authorization_response):
+ """Parse token from the URI fragment, used by MobileApplicationClients.
+
+ :param authorization_response: The full URL of the redirect back to you
+ :return: A token dict
+ """
+ self._client.parse_request_uri_response(
+ authorization_response, state=self._state
+ )
+ self.token = self._client.token
+ return self.token
+
+ def refresh_token(
+ self,
+ token_url,
+ refresh_token=None,
+ body="",
+ auth=None,
+ timeout=None,
+ headers=None,
+ verify=True,
+ proxies=None,
+ **kwargs
+ ):
+ """Fetch a new access token using a refresh token.
+
+ :param token_url: The token endpoint, must be HTTPS.
+ :param refresh_token: The refresh_token to use.
+ :param body: Optional application/x-www-form-urlencoded body to add the
+ include in the token request. Prefer kwargs over body.
+ :param auth: An auth tuple or method as accepted by `requests`.
+ :param timeout: Timeout of the request in seconds.
+ :param headers: A dict of headers to be used by `requests`.
+ :param verify: Verify SSL certificate.
+ :param proxies: The `proxies` argument will be passed to `requests`.
+ :param kwargs: Extra parameters to include in the token request.
+ :return: A token dict
+ """
+ if not token_url:
+ raise ValueError("No token endpoint set for auto_refresh.")
+
+ if not is_secure_transport(token_url):
+ raise InsecureTransportError()
+
+ refresh_token = refresh_token or self.token.get("refresh_token")
+
+ log.debug(
+ "Adding auto refresh key word arguments %s.", self.auto_refresh_kwargs
+ )
+ kwargs.update(self.auto_refresh_kwargs)
+ body = self._client.prepare_refresh_body(
+ body=body, refresh_token=refresh_token, scope=self.scope, **kwargs
+ )
+ log.debug("Prepared refresh token request body %s", body)
+
+ if headers is None:
+ headers = {
+ "Accept": "application/json",
+ "Content-Type": ("application/x-www-form-urlencoded;charset=UTF-8"),
+ }
+
+ r = self.post(
+ token_url,
+ data=dict(urldecode(body)),
+ auth=auth,
+ timeout=timeout,
+ headers=headers,
+ verify=verify,
+ withhold_token=True,
+ proxies=proxies,
+ )
+ log.debug("Request to refresh token completed with status %s.", r.status_code)
+ log.debug("Response headers were %s and content %s.", r.headers, r.text)
+ log.debug(
+ "Invoking %d token response hooks.",
+ len(self.compliance_hook["refresh_token_response"]),
+ )
+ for hook in self.compliance_hook["refresh_token_response"]:
+ log.debug("Invoking hook %s.", hook)
+ r = hook(r)
+
+ self.token = self._client.parse_request_body_response(r.text, scope=self.scope)
+ if not "refresh_token" in self.token:
+ log.debug("No new refresh token given. Re-using old.")
+ self.token["refresh_token"] = refresh_token
+ return self.token
+
+ def request(
+ self,
+ method,
+ url,
+ data=None,
+ headers=None,
+ withhold_token=False,
+ client_id=None,
+ client_secret=None,
+ **kwargs
+ ):
+ """Intercept all requests and add the OAuth 2 token if present."""
+ if not is_secure_transport(url):
+ raise InsecureTransportError()
+ if self.token and not withhold_token:
+ log.debug(
+ "Invoking %d protected resource request hooks.",
+ len(self.compliance_hook["protected_request"]),
+ )
+ for hook in self.compliance_hook["protected_request"]:
+ log.debug("Invoking hook %s.", hook)
+ url, headers, data = hook(url, headers, data)
+
+ log.debug("Adding token %s to request.", self.token)
+ try:
+ url, headers, data = self._client.add_token(
+ url, http_method=method, body=data, headers=headers
+ )
+ # Attempt to retrieve and save new access token if expired
+ except TokenExpiredError:
+ if self.auto_refresh_url:
+ log.debug(
+ "Auto refresh is set, attempting to refresh at %s.",
+ self.auto_refresh_url,
+ )
+
+ # We mustn't pass auth twice.
+ auth = kwargs.pop("auth", None)
+ if client_id and client_secret and (auth is None):
+ log.debug(
+ 'Encoding client_id "%s" with client_secret as Basic auth credentials.',
+ client_id,
+ )
+ auth = requests.auth.HTTPBasicAuth(client_id, client_secret)
+ token = self.refresh_token(
+ self.auto_refresh_url, auth=auth, **kwargs
+ )
+ if self.token_updater:
+ log.debug(
+ "Updating token to %s using %s.", token, self.token_updater
+ )
+ self.token_updater(token)
+ url, headers, data = self._client.add_token(
+ url, http_method=method, body=data, headers=headers
+ )
+ else:
+ raise TokenUpdated(token)
+ else:
+ raise
+
+ log.debug("Requesting url %s using method %s.", url, method)
+ log.debug("Supplying headers %s and data %s", headers, data)
+ log.debug("Passing through key word arguments %s.", kwargs)
+ return super(OAuth2Session, self).request(
+ method, url, headers=headers, data=data, **kwargs
+ )
+
+ def register_compliance_hook(self, hook_type, hook):
+ """Register a hook for request/response tweaking.
+
+ Available hooks are:
+ access_token_response invoked before token parsing.
+ refresh_token_response invoked before refresh token parsing.
+ protected_request invoked before making a request.
+
+ If you find a new hook is needed please send a GitHub PR request
+ or open an issue.
+ """
+ if hook_type not in self.compliance_hook:
+ raise ValueError(
+ "Hook type %s is not in %s.", hook_type, self.compliance_hook
+ )
+ self.compliance_hook[hook_type].add(hook)
diff --git a/contrib/python/requests-oauthlib/tests/__init__.py b/contrib/python/requests-oauthlib/tests/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/contrib/python/requests-oauthlib/tests/__init__.py
diff --git a/contrib/python/requests-oauthlib/tests/test.bin b/contrib/python/requests-oauthlib/tests/test.bin
new file mode 100644
index 0000000000..b00d4f4796
--- /dev/null
+++ b/contrib/python/requests-oauthlib/tests/test.bin
@@ -0,0 +1 @@
+¥Æ \ No newline at end of file
diff --git a/contrib/python/requests-oauthlib/tests/test_compliance_fixes.py b/contrib/python/requests-oauthlib/tests/test_compliance_fixes.py
new file mode 100644
index 0000000000..5c90d52660
--- /dev/null
+++ b/contrib/python/requests-oauthlib/tests/test_compliance_fixes.py
@@ -0,0 +1,334 @@
+from __future__ import unicode_literals
+from unittest import TestCase
+
+import requests
+import requests_mock
+import time
+
+try:
+ from urlparse import urlparse, parse_qs
+except ImportError:
+ from urllib.parse import urlparse, parse_qs
+
+from oauthlib.oauth2.rfc6749.errors import InvalidGrantError
+from requests_oauthlib import OAuth2Session
+from requests_oauthlib.compliance_fixes import facebook_compliance_fix
+from requests_oauthlib.compliance_fixes import fitbit_compliance_fix
+from requests_oauthlib.compliance_fixes import mailchimp_compliance_fix
+from requests_oauthlib.compliance_fixes import weibo_compliance_fix
+from requests_oauthlib.compliance_fixes import slack_compliance_fix
+from requests_oauthlib.compliance_fixes import instagram_compliance_fix
+from requests_oauthlib.compliance_fixes import plentymarkets_compliance_fix
+from requests_oauthlib.compliance_fixes import ebay_compliance_fix
+
+
+class FacebookComplianceFixTest(TestCase):
+ def setUp(self):
+ mocker = requests_mock.Mocker()
+ mocker.post(
+ "https://graph.facebook.com/oauth/access_token",
+ text="access_token=urlencoded",
+ headers={"Content-Type": "text/plain"},
+ )
+ mocker.start()
+ self.addCleanup(mocker.stop)
+
+ facebook = OAuth2Session("someclientid", redirect_uri="https://i.b")
+ self.session = facebook_compliance_fix(facebook)
+
+ def test_fetch_access_token(self):
+ token = self.session.fetch_token(
+ "https://graph.facebook.com/oauth/access_token",
+ client_secret="someclientsecret",
+ authorization_response="https://i.b/?code=hello",
+ )
+ self.assertEqual(token, {"access_token": "urlencoded", "token_type": "Bearer"})
+
+
+class FitbitComplianceFixTest(TestCase):
+ def setUp(self):
+ self.mocker = requests_mock.Mocker()
+ self.mocker.post(
+ "https://api.fitbit.com/oauth2/token",
+ json={"errors": [{"errorType": "invalid_grant"}]},
+ )
+ self.mocker.start()
+ self.addCleanup(self.mocker.stop)
+
+ fitbit = OAuth2Session("someclientid", redirect_uri="https://i.b")
+ self.session = fitbit_compliance_fix(fitbit)
+
+ def test_fetch_access_token(self):
+ self.assertRaises(
+ InvalidGrantError,
+ self.session.fetch_token,
+ "https://api.fitbit.com/oauth2/token",
+ client_secret="someclientsecret",
+ authorization_response="https://i.b/?code=hello",
+ )
+
+ self.mocker.post(
+ "https://api.fitbit.com/oauth2/token", json={"access_token": "fitbit"}
+ )
+
+ token = self.session.fetch_token(
+ "https://api.fitbit.com/oauth2/token", client_secret="good"
+ )
+
+ self.assertEqual(token, {"access_token": "fitbit"})
+
+ def test_refresh_token(self):
+ self.assertRaises(
+ InvalidGrantError,
+ self.session.refresh_token,
+ "https://api.fitbit.com/oauth2/token",
+ auth=requests.auth.HTTPBasicAuth("someclientid", "someclientsecret"),
+ )
+
+ self.mocker.post(
+ "https://api.fitbit.com/oauth2/token",
+ json={"access_token": "access", "refresh_token": "refresh"},
+ )
+
+ token = self.session.refresh_token(
+ "https://api.fitbit.com/oauth2/token",
+ auth=requests.auth.HTTPBasicAuth("someclientid", "someclientsecret"),
+ )
+
+ self.assertEqual(token["access_token"], "access")
+ self.assertEqual(token["refresh_token"], "refresh")
+
+
+class MailChimpComplianceFixTest(TestCase):
+ def setUp(self):
+ mocker = requests_mock.Mocker()
+ mocker.post(
+ "https://login.mailchimp.com/oauth2/token",
+ json={"access_token": "mailchimp", "expires_in": 0, "scope": None},
+ )
+ mocker.start()
+ self.addCleanup(mocker.stop)
+
+ mailchimp = OAuth2Session("someclientid", redirect_uri="https://i.b")
+ self.session = mailchimp_compliance_fix(mailchimp)
+
+ def test_fetch_access_token(self):
+ token = self.session.fetch_token(
+ "https://login.mailchimp.com/oauth2/token",
+ client_secret="someclientsecret",
+ authorization_response="https://i.b/?code=hello",
+ )
+ # Times should be close
+ approx_expires_at = time.time() + 3600
+ actual_expires_at = token.pop("expires_at")
+ self.assertAlmostEqual(actual_expires_at, approx_expires_at, places=2)
+
+ # Other token values exact
+ self.assertEqual(token, {"access_token": "mailchimp", "expires_in": 3600})
+
+ # And no scope at all
+ self.assertNotIn("scope", token)
+
+
+class WeiboComplianceFixTest(TestCase):
+ def setUp(self):
+ mocker = requests_mock.Mocker()
+ mocker.post(
+ "https://api.weibo.com/oauth2/access_token", json={"access_token": "weibo"}
+ )
+ mocker.start()
+ self.addCleanup(mocker.stop)
+
+ weibo = OAuth2Session("someclientid", redirect_uri="https://i.b")
+ self.session = weibo_compliance_fix(weibo)
+
+ def test_fetch_access_token(self):
+ token = self.session.fetch_token(
+ "https://api.weibo.com/oauth2/access_token",
+ client_secret="someclientsecret",
+ authorization_response="https://i.b/?code=hello",
+ )
+ self.assertEqual(token, {"access_token": "weibo", "token_type": "Bearer"})
+
+
+class SlackComplianceFixTest(TestCase):
+ def setUp(self):
+ mocker = requests_mock.Mocker()
+ mocker.post(
+ "https://slack.com/api/oauth.access",
+ json={"access_token": "xoxt-23984754863-2348975623103", "scope": "read"},
+ )
+ for method in ("GET", "POST"):
+ mocker.request(
+ method=method,
+ url="https://slack.com/api/auth.test",
+ json={
+ "ok": True,
+ "url": "https://myteam.slack.com/",
+ "team": "My Team",
+ "user": "cal",
+ "team_id": "T12345",
+ "user_id": "U12345",
+ },
+ )
+ mocker.start()
+ self.addCleanup(mocker.stop)
+
+ slack = OAuth2Session("someclientid", redirect_uri="https://i.b")
+ self.session = slack_compliance_fix(slack)
+
+ def test_protected_request(self):
+ self.session.token = {"access_token": "dummy-access-token"}
+ response = self.session.get("https://slack.com/api/auth.test")
+ url = response.request.url
+ query = parse_qs(urlparse(url).query)
+ self.assertNotIn("token", query)
+ body = response.request.body
+ data = parse_qs(body)
+ self.assertEqual(data["token"], ["dummy-access-token"])
+
+ def test_protected_request_override_token_get(self):
+ self.session.token = {"access_token": "dummy-access-token"}
+ response = self.session.get(
+ "https://slack.com/api/auth.test", data={"token": "different-token"}
+ )
+ url = response.request.url
+ query = parse_qs(urlparse(url).query)
+ self.assertNotIn("token", query)
+ body = response.request.body
+ data = parse_qs(body)
+ self.assertEqual(data["token"], ["different-token"])
+
+ def test_protected_request_override_token_post(self):
+ self.session.token = {"access_token": "dummy-access-token"}
+ response = self.session.post(
+ "https://slack.com/api/auth.test", data={"token": "different-token"}
+ )
+ url = response.request.url
+ query = parse_qs(urlparse(url).query)
+ self.assertNotIn("token", query)
+ body = response.request.body
+ data = parse_qs(body)
+ self.assertEqual(data["token"], ["different-token"])
+
+ def test_protected_request_override_token_url(self):
+ self.session.token = {"access_token": "dummy-access-token"}
+ response = self.session.get(
+ "https://slack.com/api/auth.test?token=different-token"
+ )
+ url = response.request.url
+ query = parse_qs(urlparse(url).query)
+ self.assertEqual(query["token"], ["different-token"])
+ self.assertIsNone(response.request.body)
+
+
+class InstagramComplianceFixTest(TestCase):
+ def setUp(self):
+ mocker = requests_mock.Mocker()
+ mocker.request(
+ method="GET",
+ url="https://api.instagram.com/v1/users/self",
+ json={
+ "data": {
+ "id": "1574083",
+ "username": "snoopdogg",
+ "full_name": "Snoop Dogg",
+ "profile_picture": "http://distillery.s3.amazonaws.com/profiles/profile_1574083_75sq_1295469061.jpg",
+ "bio": "This is my bio",
+ "website": "http://snoopdogg.com",
+ "is_business": False,
+ "counts": {"media": 1320, "follows": 420, "followed_by": 3410},
+ }
+ },
+ )
+ mocker.start()
+ self.addCleanup(mocker.stop)
+
+ instagram = OAuth2Session("someclientid", redirect_uri="https://i.b")
+ self.session = instagram_compliance_fix(instagram)
+
+ def test_protected_request(self):
+ self.session.token = {"access_token": "dummy-access-token"}
+ response = self.session.get("https://api.instagram.com/v1/users/self")
+ url = response.request.url
+ query = parse_qs(urlparse(url).query)
+ self.assertIn("access_token", query)
+ self.assertEqual(query["access_token"], ["dummy-access-token"])
+
+ def test_protected_request_dont_override(self):
+ """check that if the access_token param
+ already exist we don't override it"""
+ self.session.token = {"access_token": "dummy-access-token"}
+ response = self.session.get(
+ "https://api.instagram.com/v1/users/self?access_token=correct-access-token"
+ )
+ url = response.request.url
+ query = parse_qs(urlparse(url).query)
+ self.assertIn("access_token", query)
+ self.assertEqual(query["access_token"], ["correct-access-token"])
+
+
+class PlentymarketsComplianceFixTest(TestCase):
+ def setUp(self):
+ mocker = requests_mock.Mocker()
+ mocker.post(
+ "https://shop.plentymarkets-cloud02.com",
+ json={
+ "accessToken": "ecUN1r8KhJewMCdLAmpHOdZ4O0ofXKB9zf6CXK61",
+ "tokenType": "Bearer",
+ "expiresIn": 86400,
+ "refreshToken": "iG2kBGIjcXaRE4xmTVUnv7xwxX7XMcWCHqJmFaSX",
+ },
+ headers={"Content-Type": "application/json"},
+ )
+ mocker.start()
+ self.addCleanup(mocker.stop)
+
+ plentymarkets = OAuth2Session("someclientid", redirect_uri="https://i.b")
+ self.session = plentymarkets_compliance_fix(plentymarkets)
+
+ def test_fetch_access_token(self):
+ token = self.session.fetch_token(
+ "https://shop.plentymarkets-cloud02.com",
+ authorization_response="https://i.b/?code=hello",
+ )
+
+ approx_expires_at = time.time() + 86400
+ actual_expires_at = token.pop("expires_at")
+ self.assertAlmostEqual(actual_expires_at, approx_expires_at, places=2)
+
+ self.assertEqual(
+ token,
+ {
+ "access_token": "ecUN1r8KhJewMCdLAmpHOdZ4O0ofXKB9zf6CXK61",
+ "expires_in": 86400,
+ "token_type": "Bearer",
+ "refresh_token": "iG2kBGIjcXaRE4xmTVUnv7xwxX7XMcWCHqJmFaSX",
+ },
+ )
+
+
+class EbayComplianceFixTest(TestCase):
+ def setUp(self):
+ mocker = requests_mock.Mocker()
+ mocker.post(
+ "https://api.ebay.com/identity/v1/oauth2/token",
+ json={
+ "access_token": "this is the access token",
+ "expires_in": 7200,
+ "token_type": "Application Access Token",
+ },
+ headers={"Content-Type": "application/json"},
+ )
+ mocker.start()
+ self.addCleanup(mocker.stop)
+
+ session = OAuth2Session()
+ self.fixed_session = ebay_compliance_fix(session)
+
+ def test_fetch_access_token(self):
+ token = self.fixed_session.fetch_token(
+ "https://api.ebay.com/identity/v1/oauth2/token",
+ authorization_response="https://i.b/?code=hello",
+ )
+ assert token["token_type"] == "Bearer"
diff --git a/contrib/python/requests-oauthlib/tests/test_core.py b/contrib/python/requests-oauthlib/tests/test_core.py
new file mode 100644
index 0000000000..6892e9f1ce
--- /dev/null
+++ b/contrib/python/requests-oauthlib/tests/test_core.py
@@ -0,0 +1,170 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+import requests
+import requests_oauthlib
+import oauthlib
+import os.path
+from io import StringIO
+import unittest
+
+try:
+ import mock
+except ImportError:
+ from unittest import mock
+
+
+@mock.patch("oauthlib.oauth1.rfc5849.generate_timestamp")
+@mock.patch("oauthlib.oauth1.rfc5849.generate_nonce")
+class OAuth1Test(unittest.TestCase):
+ def testFormEncoded(self, generate_nonce, generate_timestamp):
+ """OAuth1 assumes form encoded if content type is not specified."""
+ generate_nonce.return_value = "abc"
+ generate_timestamp.return_value = "1"
+ oauth = requests_oauthlib.OAuth1("client_key")
+ headers = {"Content-type": "application/x-www-form-urlencoded"}
+ r = requests.Request(
+ method="POST",
+ url="http://a.b/path?query=retain",
+ auth=oauth,
+ data="this=really&is=&+form=encoded",
+ headers=headers,
+ )
+ a = r.prepare()
+
+ self.assertEqual(a.url, "http://a.b/path?query=retain")
+ self.assertEqual(a.body, b"this=really&is=&+form=encoded")
+ self.assertEqual(
+ a.headers.get("Content-Type"), b"application/x-www-form-urlencoded"
+ )
+
+ # guess content-type
+ r = requests.Request(
+ method="POST",
+ url="http://a.b/path?query=retain",
+ auth=oauth,
+ data="this=really&is=&+form=encoded",
+ )
+ b = r.prepare()
+ self.assertEqual(b.url, "http://a.b/path?query=retain")
+ self.assertEqual(b.body, b"this=really&is=&+form=encoded")
+ self.assertEqual(
+ b.headers.get("Content-Type"), b"application/x-www-form-urlencoded"
+ )
+
+ self.assertEqual(a.headers.get("Authorization"), b.headers.get("Authorization"))
+
+ def testNonFormEncoded(self, generate_nonce, generate_timestamp):
+ """OAuth signature only depend on body if it is form encoded."""
+ generate_nonce.return_value = "abc"
+ generate_timestamp.return_value = "1"
+ oauth = requests_oauthlib.OAuth1("client_key")
+
+ r = requests.Request(
+ method="POST",
+ url="http://a.b/path?query=retain",
+ auth=oauth,
+ data="this really is not form encoded",
+ )
+ a = r.prepare()
+
+ r = requests.Request(
+ method="POST", url="http://a.b/path?query=retain", auth=oauth
+ )
+ b = r.prepare()
+
+ self.assertEqual(a.headers.get("Authorization"), b.headers.get("Authorization"))
+
+ r = requests.Request(
+ method="POST",
+ url="http://a.b/path?query=retain",
+ auth=oauth,
+ files={"test": StringIO("hello")},
+ )
+ c = r.prepare()
+
+ self.assertEqual(b.headers.get("Authorization"), c.headers.get("Authorization"))
+
+ @unittest.skip("test uses real http://httpbin.org")
+ def testCanPostBinaryData(self, generate_nonce, generate_timestamp):
+ """
+ Test we can post binary data. Should prevent regression of the
+ UnicodeDecodeError issue.
+ """
+ generate_nonce.return_value = "abc"
+ generate_timestamp.return_value = "1"
+ oauth = requests_oauthlib.OAuth1("client_key")
+ import yatest.common
+ dirname = yatest.common.test_source_path()
+ fname = os.path.join(dirname, "test.bin")
+
+ with open(fname, "rb") as f:
+ r = requests.post(
+ "http://httpbin.org/post",
+ data={"hi": "there"},
+ files={"media": (os.path.basename(f.name), f)},
+ headers={"content-type": "application/octet-stream"},
+ auth=oauth,
+ )
+ self.assertEqual(r.status_code, 200)
+
+ @unittest.skip("test uses real http://httpbin.org")
+ def test_url_is_native_str(self, generate_nonce, generate_timestamp):
+ """
+ Test that the URL is always a native string.
+ """
+ generate_nonce.return_value = "abc"
+ generate_timestamp.return_value = "1"
+ oauth = requests_oauthlib.OAuth1("client_key")
+
+ r = requests.get("http://httpbin.org/get", auth=oauth)
+ self.assertIsInstance(r.request.url, str)
+
+ @unittest.skip("test uses real http://httpbin.org")
+ def test_content_type_override(self, generate_nonce, generate_timestamp):
+ """
+ Content type should only be guessed if none is given.
+ """
+ generate_nonce.return_value = "abc"
+ generate_timestamp.return_value = "1"
+ oauth = requests_oauthlib.OAuth1("client_key")
+ data = "a"
+ r = requests.post("http://httpbin.org/get", data=data, auth=oauth)
+ self.assertEqual(
+ r.request.headers.get("Content-Type"), b"application/x-www-form-urlencoded"
+ )
+ r = requests.post(
+ "http://httpbin.org/get",
+ auth=oauth,
+ data=data,
+ headers={"Content-type": "application/json"},
+ )
+ self.assertEqual(r.request.headers.get("Content-Type"), b"application/json")
+
+ def test_register_client_class(self, generate_timestamp, generate_nonce):
+ class ClientSubclass(oauthlib.oauth1.Client):
+ pass
+
+ self.assertTrue(hasattr(requests_oauthlib.OAuth1, "client_class"))
+
+ self.assertEqual(requests_oauthlib.OAuth1.client_class, oauthlib.oauth1.Client)
+
+ normal = requests_oauthlib.OAuth1("client_key")
+
+ self.assertIsInstance(normal.client, oauthlib.oauth1.Client)
+ self.assertNotIsInstance(normal.client, ClientSubclass)
+
+ requests_oauthlib.OAuth1.client_class = ClientSubclass
+
+ self.assertEqual(requests_oauthlib.OAuth1.client_class, ClientSubclass)
+
+ custom = requests_oauthlib.OAuth1("client_key")
+
+ self.assertIsInstance(custom.client, oauthlib.oauth1.Client)
+ self.assertIsInstance(custom.client, ClientSubclass)
+
+ overridden = requests_oauthlib.OAuth1(
+ "client_key", client_class=oauthlib.oauth1.Client
+ )
+
+ self.assertIsInstance(overridden.client, oauthlib.oauth1.Client)
+ self.assertNotIsInstance(normal.client, ClientSubclass)
diff --git a/contrib/python/requests-oauthlib/tests/test_oauth1_session.py b/contrib/python/requests-oauthlib/tests/test_oauth1_session.py
new file mode 100644
index 0000000000..1dd2b2f158
--- /dev/null
+++ b/contrib/python/requests-oauthlib/tests/test_oauth1_session.py
@@ -0,0 +1,348 @@
+from __future__ import unicode_literals, print_function
+import unittest
+import sys
+import requests
+from io import StringIO
+
+from oauthlib.oauth1 import SIGNATURE_TYPE_QUERY, SIGNATURE_TYPE_BODY
+from oauthlib.oauth1 import SIGNATURE_RSA, SIGNATURE_PLAINTEXT
+from requests_oauthlib import OAuth1Session
+
+try:
+ import mock
+except ImportError:
+ from unittest import mock
+
+try:
+ import cryptography
+except ImportError:
+ cryptography = None
+
+try:
+ import jwt
+except ImportError:
+ jwt = None
+
+if sys.version[0] == "3":
+ unicode_type = str
+else:
+ unicode_type = unicode
+
+
+TEST_RSA_KEY = (
+ "-----BEGIN RSA PRIVATE KEY-----\n"
+ "MIIEogIBAAKCAQEApF1JaMSN8TEsh4N4O/5SpEAVLivJyLH+Cgl3OQBPGgJkt8cg\n"
+ "49oasl+5iJS+VdrILxWM9/JCJyURpUuslX4Eb4eUBtQ0x5BaPa8+S2NLdGTaL7nB\n"
+ "OO8o8n0C5FEUU+qlEip79KE8aqOj+OC44VsIquSmOvWIQD26n3fCVlgwoRBD1gzz\n"
+ "sDOeaSyzpKrZR851Kh6rEmF2qjJ8jt6EkxMsRNACmBomzgA4M1TTsisSUO87444p\n"
+ "e35Z4/n5c735o2fZMrGgMwiJNh7rT8SYxtIkxngioiGnwkxGQxQ4NzPAHg+XSY0J\n"
+ "04pNm7KqTkgtxyrqOANJLIjXlR+U9SQ90NjHVQIDAQABAoIBABuBPOKaWcJt3yzC\n"
+ "NGGduoif7KtwSnEaUA+v69KPGa2Zju8uFHPssKD+4dZYRc2qMeunKJLpaGaSjnRh\n"
+ "yHyvvOBJCN1nr3lhz6gY5kzJTfwpUFXCOPJlGy4Q+2Xnp4YvcvYqQ9n5DVovDiZ8\n"
+ "vJOBn16xqpudMPLHIa7D5LJ8SY76HBjE+imTXw1EShdh5TOV9bmPFQqH6JFzowRH\n"
+ "hyH2DPHuyHJj6cl8FyqJw5lVWzG3n6Prvk7bYHsjmGjurN35UsumNAp6VouNyUP1\n"
+ "RAEcUJega49aIs6/FJ0ENJzQjlsAzVbTleHkpez2aIok+wsWJGJ4SVxAjADOWAaZ\n"
+ "uEJPc3UCgYEA1g4ZGrXOuo75p9/MRIepXGpBWxip4V7B9XmO9WzPCv8nMorJntWB\n"
+ "msYV1I01aITxadHatO4Gl2xLniNkDyrEQzJ7w38RQgsVK+CqbnC0K9N77QPbHeC1\n"
+ "YQd9RCNyUohOimKvb7jyv798FBU1GO5QI2eNgfnnfteSVXhD2iOoTOsCgYEAxJJ+\n"
+ "8toxJdnLa0uUsAbql6zeNXGbUBMzu3FomKlyuWuq841jS2kIalaO/TRj5hbnE45j\n"
+ "mCjeLgTVO6Ach3Wfk4zrqajqfFJ0zUg/Wexp49lC3RWiV4icBb85Q6bzeJD9Dn9v\n"
+ "hjpfWVkczf/NeA1fGH/pcgfkT6Dm706GFFttLL8CgYBl/HeXk1H47xAiHO4dJKnb\n"
+ "v0B+X8To/RXamF01r+8BpUoOubOQetdyX7ic+d6deuHu8i6LD/GSCeYJZYFR/KVg\n"
+ "AtiW757QYalnq3ZogkhFrVCZP8IRfTPOFBxp752TlyAcrSI7T9pQ47IBe4094KXM\n"
+ "CJWSfPgAJkOxd0iU0XJpmwKBgGfQxuMTgSlwYRKFlD1zKap5TdID8fbUbVnth0Q5\n"
+ "GbH7vwlp/qrxCdS/aj0n0irOpbOaW9ccnlrHiqY25VpVMLYIkt3DrDOEiNNx+KNR\n"
+ "TItdTwbcSiTYrS4L0/56ydM/H6bsfsXxRjI18hSJqMZiqXqS84OZz2aOn+h7HCzc\n"
+ "LEiZAoGASk20wFvilpRKHq79xxFWiDUPHi0x0pp82dYIEntGQkKUWkbSlhgf3MAi\n"
+ "5NEQTDmXdnB+rVeWIvEi+BXfdnNgdn8eC4zSdtF4sIAhYr5VWZo0WVWDhT7u2ccv\n"
+ "ZBFymiz8lo3gN57wGUCi9pbZqzV1+ZppX6YTNDdDCE0q+KO3Cec=\n"
+ "-----END RSA PRIVATE KEY-----"
+)
+
+TEST_RSA_OAUTH_SIGNATURE = (
+ "j8WF8PGjojT82aUDd2EL%2Bz7HCoHInFzWUpiEKMCy%2BJ2cYHWcBS7mXlmFDLgAKV0"
+ "P%2FyX4TrpXODYnJ6dRWdfghqwDpi%2FlQmB2jxCiGMdJoYxh3c5zDf26gEbGdP6D7O"
+ "Ssp5HUnzH6sNkmVjuE%2FxoJcHJdc23H6GhOs7VJ2LWNdbhKWP%2FMMlTrcoQDn8lz"
+ "%2Fb24WsJ6ae1txkUzpFOOlLM8aTdNtGL4OtsubOlRhNqnAFq93FyhXg0KjzUyIZzmMX"
+ "9Vx90jTks5QeBGYcLE0Op2iHb2u%2FO%2BEgdwFchgEwE5LgMUyHUI4F3Wglp28yHOAM"
+ "jPkI%2FkWMvpxtMrU3Z3KN31WQ%3D%3D"
+)
+
+
+class OAuth1SessionTest(unittest.TestCase):
+ def test_signature_types(self):
+ def verify_signature(getter):
+ def fake_send(r, **kwargs):
+ signature = getter(r)
+ if isinstance(signature, bytes):
+ signature = signature.decode("utf-8")
+ self.assertIn("oauth_signature", signature)
+ resp = mock.MagicMock(spec=requests.Response)
+ resp.cookies = []
+ return resp
+
+ return fake_send
+
+ header = OAuth1Session("foo")
+ header.send = verify_signature(lambda r: r.headers["Authorization"])
+ header.post("https://i.b")
+
+ query = OAuth1Session("foo", signature_type=SIGNATURE_TYPE_QUERY)
+ query.send = verify_signature(lambda r: r.url)
+ query.post("https://i.b")
+
+ body = OAuth1Session("foo", signature_type=SIGNATURE_TYPE_BODY)
+ headers = {"Content-Type": "application/x-www-form-urlencoded"}
+ body.send = verify_signature(lambda r: r.body)
+ body.post("https://i.b", headers=headers, data="")
+
+ @mock.patch("oauthlib.oauth1.rfc5849.generate_timestamp")
+ @mock.patch("oauthlib.oauth1.rfc5849.generate_nonce")
+ def test_signature_methods(self, generate_nonce, generate_timestamp):
+ if not cryptography:
+ raise unittest.SkipTest("cryptography module is required")
+ if not jwt:
+ raise unittest.SkipTest("pyjwt module is required")
+
+ generate_nonce.return_value = "abc"
+ generate_timestamp.return_value = "123"
+
+ signature = 'OAuth oauth_nonce="abc", oauth_timestamp="123", oauth_version="1.0", oauth_signature_method="HMAC-SHA1", oauth_consumer_key="foo", oauth_signature="h2sRqLArjhlc5p3FTkuNogVHlKE%3D"'
+ auth = OAuth1Session("foo")
+ auth.send = self.verify_signature(signature)
+ auth.post("https://i.b")
+
+ signature = 'OAuth oauth_nonce="abc", oauth_timestamp="123", oauth_version="1.0", oauth_signature_method="PLAINTEXT", oauth_consumer_key="foo", oauth_signature="%26"'
+ auth = OAuth1Session("foo", signature_method=SIGNATURE_PLAINTEXT)
+ auth.send = self.verify_signature(signature)
+ auth.post("https://i.b")
+
+ signature = (
+ "OAuth "
+ 'oauth_nonce="abc", oauth_timestamp="123", oauth_version="1.0", '
+ 'oauth_signature_method="RSA-SHA1", oauth_consumer_key="foo", '
+ 'oauth_signature="{sig}"'
+ ).format(sig=TEST_RSA_OAUTH_SIGNATURE)
+ auth = OAuth1Session(
+ "foo", signature_method=SIGNATURE_RSA, rsa_key=TEST_RSA_KEY
+ )
+ auth.send = self.verify_signature(signature)
+ auth.post("https://i.b")
+
+ @mock.patch("oauthlib.oauth1.rfc5849.generate_timestamp")
+ @mock.patch("oauthlib.oauth1.rfc5849.generate_nonce")
+ def test_binary_upload(self, generate_nonce, generate_timestamp):
+ generate_nonce.return_value = "abc"
+ generate_timestamp.return_value = "123"
+ fake_xml = StringIO("hello world")
+ headers = {"Content-Type": "application/xml"}
+ signature = 'OAuth oauth_nonce="abc", oauth_timestamp="123", oauth_version="1.0", oauth_signature_method="HMAC-SHA1", oauth_consumer_key="foo", oauth_signature="h2sRqLArjhlc5p3FTkuNogVHlKE%3D"'
+ auth = OAuth1Session("foo")
+ auth.send = self.verify_signature(signature)
+ auth.post("https://i.b", headers=headers, files=[("fake", fake_xml)])
+
+ @mock.patch("oauthlib.oauth1.rfc5849.generate_timestamp")
+ @mock.patch("oauthlib.oauth1.rfc5849.generate_nonce")
+ def test_nonascii(self, generate_nonce, generate_timestamp):
+ generate_nonce.return_value = "abc"
+ generate_timestamp.return_value = "123"
+ signature = 'OAuth oauth_nonce="abc", oauth_timestamp="123", oauth_version="1.0", oauth_signature_method="HMAC-SHA1", oauth_consumer_key="foo", oauth_signature="W0haoue5IZAZoaJiYCtfqwMf8x8%3D"'
+ auth = OAuth1Session("foo")
+ auth.send = self.verify_signature(signature)
+ auth.post("https://i.b?cjk=%E5%95%A6%E5%95%A6")
+
+ def test_authorization_url(self):
+ auth = OAuth1Session("foo")
+ url = "https://example.comm/authorize"
+ token = "asluif023sf"
+ auth_url = auth.authorization_url(url, request_token=token)
+ self.assertEqual(auth_url, url + "?oauth_token=" + token)
+
+ def test_parse_response_url(self):
+ url = "https://i.b/callback?oauth_token=foo&oauth_verifier=bar"
+ auth = OAuth1Session("foo")
+ resp = auth.parse_authorization_response(url)
+ self.assertEqual(resp["oauth_token"], "foo")
+ self.assertEqual(resp["oauth_verifier"], "bar")
+ for k, v in resp.items():
+ self.assertIsInstance(k, unicode_type)
+ self.assertIsInstance(v, unicode_type)
+
+ def test_fetch_request_token(self):
+ auth = OAuth1Session("foo")
+ auth.send = self.fake_body("oauth_token=foo")
+ resp = auth.fetch_request_token("https://example.com/token")
+ self.assertEqual(resp["oauth_token"], "foo")
+ for k, v in resp.items():
+ self.assertIsInstance(k, unicode_type)
+ self.assertIsInstance(v, unicode_type)
+
+ def test_fetch_request_token_with_optional_arguments(self):
+ auth = OAuth1Session("foo")
+ auth.send = self.fake_body("oauth_token=foo")
+ resp = auth.fetch_request_token(
+ "https://example.com/token", verify=False, stream=True
+ )
+ self.assertEqual(resp["oauth_token"], "foo")
+ for k, v in resp.items():
+ self.assertIsInstance(k, unicode_type)
+ self.assertIsInstance(v, unicode_type)
+
+ def test_fetch_access_token(self):
+ auth = OAuth1Session("foo", verifier="bar")
+ auth.send = self.fake_body("oauth_token=foo")
+ resp = auth.fetch_access_token("https://example.com/token")
+ self.assertEqual(resp["oauth_token"], "foo")
+ for k, v in resp.items():
+ self.assertIsInstance(k, unicode_type)
+ self.assertIsInstance(v, unicode_type)
+
+ def test_fetch_access_token_with_optional_arguments(self):
+ auth = OAuth1Session("foo", verifier="bar")
+ auth.send = self.fake_body("oauth_token=foo")
+ resp = auth.fetch_access_token(
+ "https://example.com/token", verify=False, stream=True
+ )
+ self.assertEqual(resp["oauth_token"], "foo")
+ for k, v in resp.items():
+ self.assertIsInstance(k, unicode_type)
+ self.assertIsInstance(v, unicode_type)
+
+ def _test_fetch_access_token_raises_error(self, auth):
+ """Assert that an error is being raised whenever there's no verifier
+ passed in to the client.
+ """
+ auth.send = self.fake_body("oauth_token=foo")
+ with self.assertRaises(ValueError) as cm:
+ auth.fetch_access_token("https://example.com/token")
+ self.assertEqual("No client verifier has been set.", str(cm.exception))
+
+ def test_fetch_token_invalid_response(self):
+ auth = OAuth1Session("foo")
+ auth.send = self.fake_body("not valid urlencoded response!")
+ self.assertRaises(
+ ValueError, auth.fetch_request_token, "https://example.com/token"
+ )
+
+ for code in (400, 401, 403):
+ auth.send = self.fake_body("valid=response", code)
+ with self.assertRaises(ValueError) as cm:
+ auth.fetch_request_token("https://example.com/token")
+ self.assertEqual(cm.exception.status_code, code)
+ self.assertIsInstance(cm.exception.response, requests.Response)
+
+ def test_fetch_access_token_missing_verifier(self):
+ self._test_fetch_access_token_raises_error(OAuth1Session("foo"))
+
+ def test_fetch_access_token_has_verifier_is_none(self):
+ auth = OAuth1Session("foo")
+ del auth._client.client.verifier
+ self._test_fetch_access_token_raises_error(auth)
+
+ def test_token_proxy_set(self):
+ token = {
+ "oauth_token": "fake-key",
+ "oauth_token_secret": "fake-secret",
+ "oauth_verifier": "fake-verifier",
+ }
+ sess = OAuth1Session("foo")
+ self.assertIsNone(sess._client.client.resource_owner_key)
+ self.assertIsNone(sess._client.client.resource_owner_secret)
+ self.assertIsNone(sess._client.client.verifier)
+ self.assertEqual(sess.token, {})
+
+ sess.token = token
+ self.assertEqual(sess._client.client.resource_owner_key, "fake-key")
+ self.assertEqual(sess._client.client.resource_owner_secret, "fake-secret")
+ self.assertEqual(sess._client.client.verifier, "fake-verifier")
+
+ def test_token_proxy_get(self):
+ token = {
+ "oauth_token": "fake-key",
+ "oauth_token_secret": "fake-secret",
+ "oauth_verifier": "fake-verifier",
+ }
+ sess = OAuth1Session(
+ "foo",
+ resource_owner_key=token["oauth_token"],
+ resource_owner_secret=token["oauth_token_secret"],
+ verifier=token["oauth_verifier"],
+ )
+ self.assertEqual(sess.token, token)
+
+ sess._client.client.resource_owner_key = "different-key"
+ token["oauth_token"] = "different-key"
+
+ self.assertEqual(sess.token, token)
+
+ def test_authorized_false(self):
+ sess = OAuth1Session("foo")
+ self.assertIs(sess.authorized, False)
+
+ def test_authorized_false_rsa(self):
+ signature = (
+ "OAuth "
+ 'oauth_nonce="abc", oauth_timestamp="123", oauth_version="1.0", '
+ 'oauth_signature_method="RSA-SHA1", oauth_consumer_key="foo", '
+ 'oauth_signature="{sig}"'
+ ).format(sig=TEST_RSA_OAUTH_SIGNATURE)
+ sess = OAuth1Session(
+ "foo", signature_method=SIGNATURE_RSA, rsa_key=TEST_RSA_KEY
+ )
+ sess.send = self.verify_signature(signature)
+ self.assertIs(sess.authorized, False)
+
+ def test_authorized_true(self):
+ sess = OAuth1Session("key", "secret", verifier="bar")
+ sess.send = self.fake_body("oauth_token=foo&oauth_token_secret=bar")
+ sess.fetch_access_token("https://example.com/token")
+ self.assertIs(sess.authorized, True)
+
+ @mock.patch("oauthlib.oauth1.rfc5849.generate_timestamp")
+ @mock.patch("oauthlib.oauth1.rfc5849.generate_nonce")
+ def test_authorized_true_rsa(self, generate_nonce, generate_timestamp):
+ if not cryptography:
+ raise unittest.SkipTest("cryptography module is required")
+ if not jwt:
+ raise unittest.SkipTest("pyjwt module is required")
+
+ generate_nonce.return_value = "abc"
+ generate_timestamp.return_value = "123"
+ signature = (
+ "OAuth "
+ 'oauth_nonce="abc", oauth_timestamp="123", oauth_version="1.0", '
+ 'oauth_signature_method="RSA-SHA1", oauth_consumer_key="foo", '
+ 'oauth_verifier="bar", oauth_signature="{sig}"'
+ ).format(sig=TEST_RSA_OAUTH_SIGNATURE)
+ sess = OAuth1Session(
+ "key",
+ "secret",
+ signature_method=SIGNATURE_RSA,
+ rsa_key=TEST_RSA_KEY,
+ verifier="bar",
+ )
+ sess.send = self.fake_body("oauth_token=foo&oauth_token_secret=bar")
+ sess.fetch_access_token("https://example.com/token")
+ self.assertIs(sess.authorized, True)
+
+ def verify_signature(self, signature):
+ def fake_send(r, **kwargs):
+ auth_header = r.headers["Authorization"]
+ if isinstance(auth_header, bytes):
+ auth_header = auth_header.decode("utf-8")
+ self.assertEqual(auth_header, signature)
+ resp = mock.MagicMock(spec=requests.Response)
+ resp.cookies = []
+ return resp
+
+ return fake_send
+
+ def fake_body(self, body, status_code=200):
+ def fake_send(r, **kwargs):
+ resp = mock.MagicMock(spec=requests.Response)
+ resp.cookies = []
+ resp.text = body
+ resp.status_code = status_code
+ return resp
+
+ return fake_send
diff --git a/contrib/python/requests-oauthlib/tests/test_oauth2_auth.py b/contrib/python/requests-oauthlib/tests/test_oauth2_auth.py
new file mode 100644
index 0000000000..accb561ef6
--- /dev/null
+++ b/contrib/python/requests-oauthlib/tests/test_oauth2_auth.py
@@ -0,0 +1,54 @@
+from __future__ import unicode_literals
+import unittest
+
+from oauthlib.oauth2 import WebApplicationClient, MobileApplicationClient
+from oauthlib.oauth2 import LegacyApplicationClient, BackendApplicationClient
+from requests import Request
+from requests_oauthlib import OAuth2
+
+
+class OAuth2AuthTest(unittest.TestCase):
+ def setUp(self):
+ self.token = {
+ "token_type": "Bearer",
+ "access_token": "asdfoiw37850234lkjsdfsdf",
+ "expires_in": "3600",
+ }
+ self.client_id = "foo"
+ self.clients = [
+ WebApplicationClient(self.client_id),
+ MobileApplicationClient(self.client_id),
+ LegacyApplicationClient(self.client_id),
+ BackendApplicationClient(self.client_id),
+ ]
+
+ def test_add_token_to_url(self):
+ url = "https://example.com/resource?foo=bar"
+ new_url = url + "&access_token=" + self.token["access_token"]
+ for client in self.clients:
+ client.default_token_placement = "query"
+ auth = OAuth2(client=client, token=self.token)
+ r = Request("GET", url, auth=auth).prepare()
+ self.assertEqual(r.url, new_url)
+
+ def test_add_token_to_headers(self):
+ token = "Bearer " + self.token["access_token"]
+ for client in self.clients:
+ auth = OAuth2(client=client, token=self.token)
+ r = Request("GET", "https://i.b", auth=auth).prepare()
+ self.assertEqual(r.headers["Authorization"], token)
+
+ def test_add_token_to_body(self):
+ body = "foo=bar"
+ new_body = body + "&access_token=" + self.token["access_token"]
+ for client in self.clients:
+ client.default_token_placement = "body"
+ auth = OAuth2(client=client, token=self.token)
+ r = Request("GET", "https://i.b", data=body, auth=auth).prepare()
+ self.assertEqual(r.body, new_body)
+
+ def test_add_nonexisting_token(self):
+ for client in self.clients:
+ auth = OAuth2(client=client)
+ r = Request("GET", "https://i.b", auth=auth)
+ self.assertRaises(ValueError, r.prepare)
diff --git a/contrib/python/requests-oauthlib/tests/test_oauth2_session.py b/contrib/python/requests-oauthlib/tests/test_oauth2_session.py
new file mode 100644
index 0000000000..cfc6236855
--- /dev/null
+++ b/contrib/python/requests-oauthlib/tests/test_oauth2_session.py
@@ -0,0 +1,527 @@
+from __future__ import unicode_literals
+import json
+import time
+import tempfile
+import shutil
+import os
+from base64 import b64encode
+from copy import deepcopy
+from unittest import TestCase
+
+try:
+ import mock
+except ImportError:
+ from unittest import mock
+
+from oauthlib.common import urlencode
+from oauthlib.oauth2 import TokenExpiredError, OAuth2Error
+from oauthlib.oauth2 import MismatchingStateError
+from oauthlib.oauth2 import WebApplicationClient, MobileApplicationClient
+from oauthlib.oauth2 import LegacyApplicationClient, BackendApplicationClient
+from requests_oauthlib import OAuth2Session, TokenUpdated
+import requests
+
+from requests.auth import _basic_auth_str
+
+
+fake_time = time.time()
+CODE = "asdf345xdf"
+
+
+def fake_token(token):
+ def fake_send(r, **kwargs):
+ resp = mock.MagicMock()
+ resp.text = json.dumps(token)
+ return resp
+
+ return fake_send
+
+
+class OAuth2SessionTest(TestCase):
+ def setUp(self):
+ self.token = {
+ "token_type": "Bearer",
+ "access_token": "asdfoiw37850234lkjsdfsdf",
+ "refresh_token": "sldvafkjw34509s8dfsdf",
+ "expires_in": 3600,
+ "expires_at": fake_time + 3600,
+ }
+ # use someclientid:someclientsecret to easily differentiate between client and user credentials
+ # these are the values used in oauthlib tests
+ self.client_id = "someclientid"
+ self.client_secret = "someclientsecret"
+ self.user_username = "user_username"
+ self.user_password = "user_password"
+ self.client_WebApplication = WebApplicationClient(self.client_id, code=CODE)
+ self.client_LegacyApplication = LegacyApplicationClient(self.client_id)
+ self.client_BackendApplication = BackendApplicationClient(self.client_id)
+ self.client_MobileApplication = MobileApplicationClient(self.client_id)
+ self.clients = [
+ self.client_WebApplication,
+ self.client_LegacyApplication,
+ self.client_BackendApplication,
+ ]
+ self.all_clients = self.clients + [self.client_MobileApplication]
+
+ def test_add_token(self):
+ token = "Bearer " + self.token["access_token"]
+
+ def verifier(r, **kwargs):
+ auth_header = r.headers.get(str("Authorization"), None)
+ self.assertEqual(auth_header, token)
+ resp = mock.MagicMock()
+ resp.cookes = []
+ return resp
+
+ for client in self.all_clients:
+ sess = OAuth2Session(client=client, token=self.token)
+ sess.send = verifier
+ sess.get("https://i.b")
+
+ def test_mtls(self):
+ cert = (
+ "testsomething.example-client.pem",
+ "testsomething.example-client-key.pem",
+ )
+
+ def verifier(r, **kwargs):
+ self.assertIn("cert", kwargs)
+ self.assertEqual(cert, kwargs["cert"])
+ self.assertIn("client_id=" + self.client_id, r.body)
+ resp = mock.MagicMock()
+ resp.text = json.dumps(self.token)
+ return resp
+
+ for client in self.clients:
+ sess = OAuth2Session(client=client)
+ sess.send = verifier
+
+ if isinstance(client, LegacyApplicationClient):
+ sess.fetch_token(
+ "https://i.b",
+ include_client_id=True,
+ cert=cert,
+ username="username1",
+ password="password1",
+ )
+ else:
+ sess.fetch_token("https://i.b", include_client_id=True, cert=cert)
+
+ def test_authorization_url(self):
+ url = "https://example.com/authorize?foo=bar"
+
+ web = WebApplicationClient(self.client_id)
+ s = OAuth2Session(client=web)
+ auth_url, state = s.authorization_url(url)
+ self.assertIn(state, auth_url)
+ self.assertIn(self.client_id, auth_url)
+ self.assertIn("response_type=code", auth_url)
+
+ mobile = MobileApplicationClient(self.client_id)
+ s = OAuth2Session(client=mobile)
+ auth_url, state = s.authorization_url(url)
+ self.assertIn(state, auth_url)
+ self.assertIn(self.client_id, auth_url)
+ self.assertIn("response_type=token", auth_url)
+
+ @mock.patch("time.time", new=lambda: fake_time)
+ def test_refresh_token_request(self):
+ self.expired_token = dict(self.token)
+ self.expired_token["expires_in"] = "-1"
+ del self.expired_token["expires_at"]
+
+ def fake_refresh(r, **kwargs):
+ if "/refresh" in r.url:
+ self.assertNotIn("Authorization", r.headers)
+ resp = mock.MagicMock()
+ resp.text = json.dumps(self.token)
+ return resp
+
+ # No auto refresh setup
+ for client in self.clients:
+ sess = OAuth2Session(client=client, token=self.expired_token)
+ self.assertRaises(TokenExpiredError, sess.get, "https://i.b")
+
+ # Auto refresh but no auto update
+ for client in self.clients:
+ sess = OAuth2Session(
+ client=client,
+ token=self.expired_token,
+ auto_refresh_url="https://i.b/refresh",
+ )
+ sess.send = fake_refresh
+ self.assertRaises(TokenUpdated, sess.get, "https://i.b")
+
+ # Auto refresh and auto update
+ def token_updater(token):
+ self.assertEqual(token, self.token)
+
+ for client in self.clients:
+ sess = OAuth2Session(
+ client=client,
+ token=self.expired_token,
+ auto_refresh_url="https://i.b/refresh",
+ token_updater=token_updater,
+ )
+ sess.send = fake_refresh
+ sess.get("https://i.b")
+
+ def fake_refresh_with_auth(r, **kwargs):
+ if "/refresh" in r.url:
+ self.assertIn("Authorization", r.headers)
+ encoded = b64encode(
+ "{client_id}:{client_secret}".format(
+ client_id=self.client_id, client_secret=self.client_secret
+ ).encode("latin1")
+ )
+ content = "Basic {encoded}".format(encoded=encoded.decode("latin1"))
+ self.assertEqual(r.headers["Authorization"], content)
+ resp = mock.MagicMock()
+ resp.text = json.dumps(self.token)
+ return resp
+
+ for client in self.clients:
+ sess = OAuth2Session(
+ client=client,
+ token=self.expired_token,
+ auto_refresh_url="https://i.b/refresh",
+ token_updater=token_updater,
+ )
+ sess.send = fake_refresh_with_auth
+ sess.get(
+ "https://i.b",
+ client_id=self.client_id,
+ client_secret=self.client_secret,
+ )
+
+ @mock.patch("time.time", new=lambda: fake_time)
+ def test_token_from_fragment(self):
+ mobile = MobileApplicationClient(self.client_id)
+ response_url = "https://i.b/callback#" + urlencode(self.token.items())
+ sess = OAuth2Session(client=mobile)
+ self.assertEqual(sess.token_from_fragment(response_url), self.token)
+
+ @mock.patch("time.time", new=lambda: fake_time)
+ def test_fetch_token(self):
+ url = "https://example.com/token"
+
+ for client in self.clients:
+ sess = OAuth2Session(client=client, token=self.token)
+ sess.send = fake_token(self.token)
+ if isinstance(client, LegacyApplicationClient):
+ # this client requires a username+password
+ # if unset, an error will be raised
+ self.assertRaises(ValueError, sess.fetch_token, url)
+ self.assertRaises(
+ ValueError, sess.fetch_token, url, username="username1"
+ )
+ self.assertRaises(
+ ValueError, sess.fetch_token, url, password="password1"
+ )
+ # otherwise it will pass
+ self.assertEqual(
+ sess.fetch_token(url, username="username1", password="password1"),
+ self.token,
+ )
+ else:
+ self.assertEqual(sess.fetch_token(url), self.token)
+
+ error = {"error": "invalid_request"}
+ for client in self.clients:
+ sess = OAuth2Session(client=client, token=self.token)
+ sess.send = fake_token(error)
+ if isinstance(client, LegacyApplicationClient):
+ # this client requires a username+password
+ # if unset, an error will be raised
+ self.assertRaises(ValueError, sess.fetch_token, url)
+ self.assertRaises(
+ ValueError, sess.fetch_token, url, username="username1"
+ )
+ self.assertRaises(
+ ValueError, sess.fetch_token, url, password="password1"
+ )
+ # otherwise it will pass
+ self.assertRaises(
+ OAuth2Error,
+ sess.fetch_token,
+ url,
+ username="username1",
+ password="password1",
+ )
+ else:
+ self.assertRaises(OAuth2Error, sess.fetch_token, url)
+
+ # there are different scenarios in which the `client_id` can be specified
+ # reference `oauthlib.tests.oauth2.rfc6749.clients.test_web_application.WebApplicationClientTest.test_prepare_request_body`
+ # this only needs to test WebApplicationClient
+ client = self.client_WebApplication
+ client.tester = True
+
+ # this should be a tuple of (r.url, r.body, r.headers.get('Authorization'))
+ _fetch_history = []
+
+ def fake_token_history(token):
+ def fake_send(r, **kwargs):
+ resp = mock.MagicMock()
+ resp.text = json.dumps(token)
+ _fetch_history.append(
+ (r.url, r.body, r.headers.get("Authorization", None))
+ )
+ return resp
+
+ return fake_send
+
+ sess = OAuth2Session(client=client, token=self.token)
+ sess.send = fake_token_history(self.token)
+ expected_auth_header = _basic_auth_str(self.client_id, self.client_secret)
+
+ # scenario 1 - default request
+ # this should send the `client_id` in the headers, as that is recommended by the RFC
+ self.assertEqual(
+ sess.fetch_token(url, client_secret="someclientsecret"), self.token
+ )
+ self.assertEqual(len(_fetch_history), 1)
+ self.assertNotIn(
+ "client_id", _fetch_history[0][1]
+ ) # no `client_id` in the body
+ self.assertNotIn(
+ "client_secret", _fetch_history[0][1]
+ ) # no `client_secret` in the body
+ self.assertEqual(
+ _fetch_history[0][2], expected_auth_header
+ ) # ensure a Basic Authorization header
+
+ # scenario 2 - force the `client_id` into the body
+ self.assertEqual(
+ sess.fetch_token(
+ url, client_secret="someclientsecret", include_client_id=True
+ ),
+ self.token,
+ )
+ self.assertEqual(len(_fetch_history), 2)
+ self.assertIn("client_id=%s" % self.client_id, _fetch_history[1][1])
+ self.assertIn("client_secret=%s" % self.client_secret, _fetch_history[1][1])
+ self.assertEqual(
+ _fetch_history[1][2], None
+ ) # ensure NO Basic Authorization header
+
+ # scenario 3 - send in an auth object
+ auth = requests.auth.HTTPBasicAuth(self.client_id, self.client_secret)
+ self.assertEqual(sess.fetch_token(url, auth=auth), self.token)
+ self.assertEqual(len(_fetch_history), 3)
+ self.assertNotIn(
+ "client_id", _fetch_history[2][1]
+ ) # no `client_id` in the body
+ self.assertNotIn(
+ "client_secret", _fetch_history[2][1]
+ ) # no `client_secret` in the body
+ self.assertEqual(
+ _fetch_history[2][2], expected_auth_header
+ ) # ensure a Basic Authorization header
+
+ # scenario 4 - send in a username/password combo
+ # this should send the `client_id` in the headers, like scenario 1
+ self.assertEqual(
+ sess.fetch_token(
+ url, username=self.user_username, password=self.user_password
+ ),
+ self.token,
+ )
+ self.assertEqual(len(_fetch_history), 4)
+ self.assertNotIn(
+ "client_id", _fetch_history[3][1]
+ ) # no `client_id` in the body
+ self.assertNotIn(
+ "client_secret", _fetch_history[3][1]
+ ) # no `client_secret` in the body
+ self.assertEqual(
+ _fetch_history[0][2], expected_auth_header
+ ) # ensure a Basic Authorization header
+ self.assertIn("username=%s" % self.user_username, _fetch_history[3][1])
+ self.assertIn("password=%s" % self.user_password, _fetch_history[3][1])
+
+ # scenario 5 - send data in `params` and not in `data` for providers
+ # that expect data in URL
+ self.assertEqual(
+ sess.fetch_token(url, client_secret="somesecret", force_querystring=True),
+ self.token,
+ )
+ self.assertIn("code=%s" % CODE, _fetch_history[4][0])
+
+ # some quick tests for valid ways of supporting `client_secret`
+
+ # scenario 2b - force the `client_id` into the body; but the `client_secret` is `None`
+ self.assertEqual(
+ sess.fetch_token(url, client_secret=None, include_client_id=True),
+ self.token,
+ )
+ self.assertEqual(len(_fetch_history), 6)
+ self.assertIn("client_id=%s" % self.client_id, _fetch_history[5][1])
+ self.assertNotIn(
+ "client_secret=", _fetch_history[5][1]
+ ) # no `client_secret` in the body
+ self.assertEqual(
+ _fetch_history[5][2], None
+ ) # ensure NO Basic Authorization header
+
+ # scenario 2c - force the `client_id` into the body; but the `client_secret` is an empty string
+ self.assertEqual(
+ sess.fetch_token(url, client_secret="", include_client_id=True), self.token
+ )
+ self.assertEqual(len(_fetch_history), 7)
+ self.assertIn("client_id=%s" % self.client_id, _fetch_history[6][1])
+ self.assertIn("client_secret=", _fetch_history[6][1])
+ self.assertEqual(
+ _fetch_history[6][2], None
+ ) # ensure NO Basic Authorization header
+
+ def test_cleans_previous_token_before_fetching_new_one(self):
+ """Makes sure the previous token is cleaned before fetching a new one.
+
+ The reason behind it is that, if the previous token is expired, this
+ method shouldn't fail with a TokenExpiredError, since it's attempting
+ to get a new one (which shouldn't be expired).
+
+ """
+ new_token = deepcopy(self.token)
+ past = time.time() - 7200
+ now = time.time()
+ self.token["expires_at"] = past
+ new_token["expires_at"] = now + 3600
+ url = "https://example.com/token"
+
+ with mock.patch("time.time", lambda: now):
+ for client in self.clients:
+ sess = OAuth2Session(client=client, token=self.token)
+ sess.send = fake_token(new_token)
+ if isinstance(client, LegacyApplicationClient):
+ # this client requires a username+password
+ # if unset, an error will be raised
+ self.assertRaises(ValueError, sess.fetch_token, url)
+ self.assertRaises(
+ ValueError, sess.fetch_token, url, username="username1"
+ )
+ self.assertRaises(
+ ValueError, sess.fetch_token, url, password="password1"
+ )
+ # otherwise it will pass
+ self.assertEqual(
+ sess.fetch_token(
+ url, username="username1", password="password1"
+ ),
+ new_token,
+ )
+ else:
+ self.assertEqual(sess.fetch_token(url), new_token)
+
+ def test_web_app_fetch_token(self):
+ # Ensure the state parameter is used, see issue #105.
+ client = OAuth2Session("someclientid", state="somestate")
+ self.assertRaises(
+ MismatchingStateError,
+ client.fetch_token,
+ "https://i.b/token",
+ authorization_response="https://i.b/no-state?code=abc",
+ )
+
+ def test_client_id_proxy(self):
+ sess = OAuth2Session("test-id")
+ self.assertEqual(sess.client_id, "test-id")
+ sess.client_id = "different-id"
+ self.assertEqual(sess.client_id, "different-id")
+ sess._client.client_id = "something-else"
+ self.assertEqual(sess.client_id, "something-else")
+ del sess.client_id
+ self.assertIsNone(sess.client_id)
+
+ def test_access_token_proxy(self):
+ sess = OAuth2Session("test-id")
+ self.assertIsNone(sess.access_token)
+ sess.access_token = "test-token"
+ self.assertEqual(sess.access_token, "test-token")
+ sess._client.access_token = "different-token"
+ self.assertEqual(sess.access_token, "different-token")
+ del sess.access_token
+ self.assertIsNone(sess.access_token)
+
+ def test_token_proxy(self):
+ token = {"access_token": "test-access"}
+ sess = OAuth2Session("test-id", token=token)
+ self.assertEqual(sess.access_token, "test-access")
+ self.assertEqual(sess.token, token)
+ token["access_token"] = "something-else"
+ sess.token = token
+ self.assertEqual(sess.access_token, "something-else")
+ self.assertEqual(sess.token, token)
+ sess._client.access_token = "different-token"
+ token["access_token"] = "different-token"
+ self.assertEqual(sess.access_token, "different-token")
+ self.assertEqual(sess.token, token)
+ # can't delete token attribute
+ with self.assertRaises(AttributeError):
+ del sess.token
+
+ def test_authorized_false(self):
+ sess = OAuth2Session("someclientid")
+ self.assertFalse(sess.authorized)
+
+ @mock.patch("time.time", new=lambda: fake_time)
+ def test_authorized_true(self):
+ def fake_token(token):
+ def fake_send(r, **kwargs):
+ resp = mock.MagicMock()
+ resp.text = json.dumps(token)
+ return resp
+
+ return fake_send
+
+ url = "https://example.com/token"
+
+ for client in self.clients:
+ sess = OAuth2Session(client=client)
+ sess.send = fake_token(self.token)
+ self.assertFalse(sess.authorized)
+ if isinstance(client, LegacyApplicationClient):
+ # this client requires a username+password
+ # if unset, an error will be raised
+ self.assertRaises(ValueError, sess.fetch_token, url)
+ self.assertRaises(
+ ValueError, sess.fetch_token, url, username="username1"
+ )
+ self.assertRaises(
+ ValueError, sess.fetch_token, url, password="password1"
+ )
+ # otherwise it will pass
+ sess.fetch_token(url, username="username1", password="password1")
+ else:
+ sess.fetch_token(url)
+ self.assertTrue(sess.authorized)
+
+
+class OAuth2SessionNetrcTest(OAuth2SessionTest):
+ """Ensure that there is no magic auth handling.
+
+ By default, requests sessions have magic handling of netrc files,
+ which is undesirable for this library because it will take
+ precedence over manually set authentication headers.
+ """
+
+ def setUp(self):
+ # Set up a temporary home directory
+ self.homedir = tempfile.mkdtemp()
+ self.prehome = os.environ.get("HOME", None)
+ os.environ["HOME"] = self.homedir
+
+ # Write a .netrc file that will cause problems
+ netrc_loc = os.path.expanduser("~/.netrc")
+ with open(netrc_loc, "w") as f:
+ f.write("machine i.b\n" " password abc123\n" " login spam@eggs.co\n")
+
+ super(OAuth2SessionNetrcTest, self).setUp()
+
+ def tearDown(self):
+ super(OAuth2SessionNetrcTest, self).tearDown()
+
+ if self.prehome is not None:
+ os.environ["HOME"] = self.prehome
+ shutil.rmtree(self.homedir)
diff --git a/contrib/python/requests-oauthlib/tests/ya.make b/contrib/python/requests-oauthlib/tests/ya.make
new file mode 100644
index 0000000000..a8f7328ae7
--- /dev/null
+++ b/contrib/python/requests-oauthlib/tests/ya.make
@@ -0,0 +1,28 @@
+PY3TEST()
+
+PEERDIR(
+ contrib/python/requests-oauthlib
+ contrib/python/requests-mock
+)
+
+# These tests use real http://httpbin.org that is why they are disabled:
+# testCanPostBinaryData
+# test_url_is_native_str
+# test_content_type_override
+
+TEST_SRCS(
+ __init__.py
+ test_compliance_fixes.py
+ test_core.py
+ test_oauth1_session.py
+ test_oauth2_auth.py
+ test_oauth2_session.py
+)
+
+DATA(
+ arcadia/contrib/python/requests-oauthlib/tests
+)
+
+NO_LINT()
+
+END()
diff --git a/contrib/python/requests-oauthlib/ya.make b/contrib/python/requests-oauthlib/ya.make
new file mode 100644
index 0000000000..2145e60cc4
--- /dev/null
+++ b/contrib/python/requests-oauthlib/ya.make
@@ -0,0 +1,45 @@
+# Generated by devtools/yamaker (pypi).
+
+PY3_LIBRARY()
+
+VERSION(1.3.1)
+
+LICENSE(ISC)
+
+PEERDIR(
+ contrib/python/oauthlib
+ contrib/python/requests
+)
+
+NO_LINT()
+
+PY_SRCS(
+ TOP_LEVEL
+ requests_oauthlib/__init__.py
+ requests_oauthlib/compliance_fixes/__init__.py
+ requests_oauthlib/compliance_fixes/douban.py
+ requests_oauthlib/compliance_fixes/ebay.py
+ requests_oauthlib/compliance_fixes/facebook.py
+ requests_oauthlib/compliance_fixes/fitbit.py
+ requests_oauthlib/compliance_fixes/instagram.py
+ requests_oauthlib/compliance_fixes/mailchimp.py
+ requests_oauthlib/compliance_fixes/plentymarkets.py
+ requests_oauthlib/compliance_fixes/slack.py
+ requests_oauthlib/compliance_fixes/weibo.py
+ requests_oauthlib/oauth1_auth.py
+ requests_oauthlib/oauth1_session.py
+ requests_oauthlib/oauth2_auth.py
+ requests_oauthlib/oauth2_session.py
+)
+
+RESOURCE_FILES(
+ PREFIX contrib/python/requests-oauthlib/
+ .dist-info/METADATA
+ .dist-info/top_level.txt
+)
+
+END()
+
+RECURSE_FOR_TESTS(
+ tests
+)