Import Upstream version 3.2.2
This commit is contained in:
parent
5d789ab01a
commit
dabc06c9f7
121
CHANGELOG.rst
121
CHANGELOG.rst
|
@ -1,33 +1,112 @@
|
|||
Changelog
|
||||
=========
|
||||
|
||||
3.1.0 (TBD)
|
||||
3.2.2 (2022-10-17)
|
||||
------------------
|
||||
OAuth2.0 Provider:
|
||||
* CVE-2022-36087
|
||||
|
||||
OAuth2.0 Provider - Features
|
||||
* #660: OIDC add support of nonce, c_hash, at_hash fields
|
||||
- New RequestValidator.fill_id_token method
|
||||
- Deprecated RequestValidator.get_id_token method
|
||||
* #677: OIDC add UserInfo endpoint
|
||||
- New RequestValidator.get_userinfo_claims method
|
||||
3.2.1 (2022-09-09)
|
||||
------------------
|
||||
OAuth2.0 Provider:
|
||||
* #803: Metadata endpoint support of non-HTTPS
|
||||
|
||||
OAuth2.0 Provider - Security
|
||||
* #665: Enhance data leak to logs
|
||||
- New default to not expose request content in logs
|
||||
- New function `oauthlib.set_debug(True)`
|
||||
* #666: Disabling query parameters for POST requests
|
||||
OAuth1.0:
|
||||
* #818: Allow IPv6 being parsed by signature
|
||||
|
||||
General:
|
||||
* Improved and fixed documentation warnings.
|
||||
* Cosmetic changes based on isort
|
||||
|
||||
3.2.0 (2022-01-29)
|
||||
------------------
|
||||
OAuth2.0 Client:
|
||||
* #795: Add Device Authorization Flow for Web Application
|
||||
* #786: Add PKCE support for Client
|
||||
* #783: Fallback to none in case of wrong expires_at format.
|
||||
|
||||
OAuth2.0 Provider:
|
||||
* #790: Add support for CORS to metadata endpoint.
|
||||
* #791: Add support for CORS to token endpoint.
|
||||
* #787: Remove comma after Bearer in WWW-Authenticate
|
||||
|
||||
OAuth2.0 Provider - OIDC:
|
||||
* #755: Call save_token in Hybrid code flow
|
||||
* #751: OIDC add support of refreshing ID Tokens with `refresh_id_token`
|
||||
* #751: The RefreshTokenGrant modifiers now take the same arguments as the
|
||||
AuthorizationCodeGrant modifiers (`token`, `token_handler`, `request`).
|
||||
|
||||
General:
|
||||
* Added Python 3.9, 3.10, 3.11
|
||||
* Improve Travis & Coverage
|
||||
|
||||
3.1.1 (2021-05-31)
|
||||
------------------
|
||||
OAuth2.0 Provider - Bugfixes
|
||||
* #670: Fix validate_authorization_request to return the new PKCE fields
|
||||
* #674: Fix token_type to be case-insensitive (bearer and Bearer)
|
||||
|
||||
* #753: Fix acceptance of valid IPv6 addresses in URI validation
|
||||
|
||||
OAuth2.0 Client - Bugfixes
|
||||
* #290: Fix Authorization Code's errors processing
|
||||
* #603: BackendApplication.Client.prepare_request_body use the "scope" argument as intended.
|
||||
* #672: Fix edge case when expires_in=Null
|
||||
|
||||
* #730: Base OAuth2 Client now has a consistent way of managing the `scope`: it consistently
|
||||
relies on the `scope` provided in the constructor if any, except if overridden temporarily
|
||||
in a method call. Note that in particular providing a non-None `scope` in
|
||||
`prepare_authorization_request` or `prepare_refresh_token` does not override anymore
|
||||
`self.scope` forever, it is just used temporarily.
|
||||
* #726: MobileApplicationClient.prepare_request_uri and MobileApplicationClient.parse_request_uri_response,
|
||||
ServiceApplicationClient.prepare_request_body,
|
||||
and WebApplicationClient.prepare_request_uri now correctly use the default `scope` provided in
|
||||
constructor.
|
||||
* #725: LegacyApplicationClient.prepare_request_body now correctly uses the default `scope` provided in constructor
|
||||
|
||||
OAuth2.0 Provider - Bugfixes
|
||||
* #711: client_credentials grant: fix log message
|
||||
* #746: OpenID Connect Hybrid - fix nonce not passed to add_id_token
|
||||
* #756: Different prompt values are now handled according to spec (e.g. prompt=none)
|
||||
* #759: OpenID Connect - fix Authorization: Basic parsing
|
||||
|
||||
General
|
||||
* #716: improved skeleton validator for public vs private client
|
||||
* #720: replace mock library with standard unittest.mock
|
||||
* #727: build isort integration
|
||||
* #734: python2 code removal
|
||||
* #735, #750: add python3.8 support
|
||||
* #749: bump minimum versions of pyjwt and cryptography
|
||||
|
||||
3.1.0 (2019-08-06)
|
||||
------------------
|
||||
OAuth2.0 Provider - Features
|
||||
|
||||
* #660: OIDC add support of `nonce`, `c_hash`, `at_hash fields`
|
||||
- New `RequestValidator.fill_id_token` method
|
||||
- Deprecated `RequestValidator.get_id_token` method
|
||||
* #677: OIDC add `UserInfo` endpoint - New `RequestValidator.get_userinfo_claims` method
|
||||
|
||||
OAuth2.0 Provider - Security
|
||||
|
||||
* #665: Enhance data leak to logs
|
||||
* New default to not expose request content in logs
|
||||
* New function `oauthlib.set_debug(True)`
|
||||
* #666: Disabling query parameters for POST requests
|
||||
|
||||
OAuth2.0 Provider - Bugfixes
|
||||
|
||||
* #670: Fix `validate_authorization_request` to return the new PKCE fields
|
||||
* #674: Fix `token_type` to be case-insensitive (`bearer` and `Bearer`)
|
||||
|
||||
OAuth2.0 Client - Bugfixes
|
||||
|
||||
* #290: Fix Authorization Code's errors processing
|
||||
* #603: BackendApplicationClient.prepare_request_body use the `scope` argument as intended.
|
||||
* #672: Fix edge case when `expires_in=Null`
|
||||
|
||||
OAuth1.0 Client
|
||||
* #669: Add case-insensitive headers to oauth1 BaseEndpoint
|
||||
|
||||
* #669: Add case-insensitive headers to oauth1 `BaseEndpoint`
|
||||
|
||||
OAuth1.0
|
||||
|
||||
* #722: Added support for HMAC-SHA512, RSA-SHA256 and RSA-SHA512 signature methods.
|
||||
|
||||
3.0.2 (2019-07-04)
|
||||
------------------
|
||||
|
@ -84,7 +163,7 @@ OAuth1.0 Client:
|
|||
General fixes:
|
||||
|
||||
* $ and ' are allowed to be unencoded in query strings #564
|
||||
* Request attributes are no longer overriden by HTTP Headers #409
|
||||
* Request attributes are no longer overridden by HTTP Headers #409
|
||||
* Removed unnecessary code for handling python2.6
|
||||
* Add support of python3.7 #621
|
||||
* Several minors updates to setup.py and tox
|
||||
|
@ -142,7 +221,7 @@ General fixes:
|
|||
* Added log statements to except clauses.
|
||||
* According to RC7009 Section 2.1, a client should include authentication credentials when revoking its tokens.
|
||||
As discussed in #339, this is not make sense for public clients.
|
||||
However, in that case, the public client should still be checked that is infact a public client (authenticate_client_id).
|
||||
However, in that case, the public client should still be checked that is in fact a public client (authenticate_client_id).
|
||||
* Improved prompt parameter validation.
|
||||
* Added two error codes from RFC 6750.
|
||||
* Hybrid response types are now be fragment-encoded.
|
||||
|
@ -292,7 +371,7 @@ Quick fix. OAuth 1 client repr in 0.6.2 overwrote secrets when scrubbing for pri
|
|||
Draft revocation endpoint features and numerous fixes including:
|
||||
|
||||
* (OAuth 2 Provider) is_within_original_scope to check whether a refresh token
|
||||
is trying to aquire a new set of scopes that are a subset of the original scope.
|
||||
is trying to acquire a new set of scopes that are a subset of the original scope.
|
||||
|
||||
* (OAuth 2 Provider) expires_in token lifetime can be set per request.
|
||||
|
||||
|
|
286
PKG-INFO
286
PKG-INFO
|
@ -1,6 +1,6 @@
|
|||
Metadata-Version: 2.1
|
||||
Name: oauthlib
|
||||
Version: 3.1.0
|
||||
Version: 3.2.2
|
||||
Summary: A generic, spec-compliant, thorough implementation of the OAuth request-signing logic
|
||||
Home-page: https://github.com/oauthlib/oauthlib
|
||||
Author: The OAuthlib Community
|
||||
|
@ -8,138 +8,6 @@ Author-email: idan@gazit.me
|
|||
Maintainer: Ib Lundgren
|
||||
Maintainer-email: ib.lundgren@gmail.com
|
||||
License: BSD
|
||||
Description: OAuthLib - Python Framework for OAuth1 & OAuth2
|
||||
===============================================
|
||||
|
||||
*A generic, spec-compliant, thorough implementation of the OAuth request-signing
|
||||
logic for Python 2.7 and 3.4+.*
|
||||
|
||||
.. image:: https://travis-ci.org/oauthlib/oauthlib.svg?branch=master
|
||||
:target: https://travis-ci.org/oauthlib/oauthlib
|
||||
:alt: Travis
|
||||
.. image:: https://coveralls.io/repos/oauthlib/oauthlib/badge.svg?branch=master
|
||||
:target: https://coveralls.io/r/oauthlib/oauthlib
|
||||
:alt: Coveralls
|
||||
.. image:: https://img.shields.io/pypi/pyversions/oauthlib.svg
|
||||
:target: https://pypi.org/project/oauthlib/
|
||||
:alt: Download from PyPI
|
||||
.. image:: https://img.shields.io/pypi/l/oauthlib.svg
|
||||
:target: https://pypi.org/project/oauthlib/
|
||||
:alt: License
|
||||
.. image:: https://app.fossa.io/api/projects/git%2Bgithub.com%2Foauthlib%2Foauthlib.svg?type=shield
|
||||
:target: https://app.fossa.io/projects/git%2Bgithub.com%2Foauthlib%2Foauthlib?ref=badge_shield
|
||||
:alt: FOSSA Status
|
||||
.. image:: https://img.shields.io/readthedocs/oauthlib.svg
|
||||
:target: https://oauthlib.readthedocs.io/en/latest/index.html
|
||||
:alt: Read the Docs
|
||||
.. image:: https://badges.gitter.im/oauthlib/oauthlib.svg
|
||||
:target: https://gitter.im/oauthlib/Lobby
|
||||
:alt: Chat on Gitter
|
||||
|
||||
OAuth often seems complicated and difficult-to-implement. There are several
|
||||
prominent libraries for handling OAuth requests, but they all suffer from one or
|
||||
both of the following:
|
||||
|
||||
1. They predate the `OAuth 1.0 spec`_, AKA RFC 5849.
|
||||
2. They predate the `OAuth 2.0 spec`_, AKA RFC 6749.
|
||||
3. They assume the usage of a specific HTTP request library.
|
||||
|
||||
.. _`OAuth 1.0 spec`: https://tools.ietf.org/html/rfc5849
|
||||
.. _`OAuth 2.0 spec`: https://tools.ietf.org/html/rfc6749
|
||||
|
||||
OAuthLib is a framework which implements the logic of OAuth1 or OAuth2 without
|
||||
assuming a specific HTTP request object or web framework. Use it to graft OAuth
|
||||
client support onto your favorite HTTP library, or provide support onto your
|
||||
favourite web framework. If you're a maintainer of such a library, write a thin
|
||||
veneer on top of OAuthLib and get OAuth support for very little effort.
|
||||
|
||||
|
||||
Documentation
|
||||
--------------
|
||||
|
||||
Full documentation is available on `Read the Docs`_. All contributions are very
|
||||
welcome! The documentation is still quite sparse, please open an issue for what
|
||||
you'd like to know, or discuss it in our `Gitter community`_, or even better, send a
|
||||
pull request!
|
||||
|
||||
.. _`Gitter community`: https://gitter.im/oauthlib/Lobby
|
||||
.. _`Read the Docs`: https://oauthlib.readthedocs.io/en/latest/index.html
|
||||
|
||||
Interested in making OAuth requests?
|
||||
------------------------------------
|
||||
|
||||
Then you might be more interested in using `requests`_ which has OAuthLib
|
||||
powered OAuth support provided by the `requests-oauthlib`_ library.
|
||||
|
||||
.. _`requests`: https://github.com/requests/requests
|
||||
.. _`requests-oauthlib`: https://github.com/requests/requests-oauthlib
|
||||
|
||||
Which web frameworks are supported?
|
||||
-----------------------------------
|
||||
|
||||
The following packages provide OAuth support using OAuthLib.
|
||||
|
||||
- For Django there is `django-oauth-toolkit`_, which includes `Django REST framework`_ support.
|
||||
- For Flask there is `flask-oauthlib`_ and `Flask-Dance`_.
|
||||
- For Pyramid there is `pyramid-oauthlib`_.
|
||||
- For Bottle there is `bottle-oauthlib`_.
|
||||
|
||||
If you have written an OAuthLib package that supports your favorite framework,
|
||||
please open a Pull Request, updating the documentation.
|
||||
|
||||
.. _`django-oauth-toolkit`: https://github.com/evonove/django-oauth-toolkit
|
||||
.. _`flask-oauthlib`: https://github.com/lepture/flask-oauthlib
|
||||
.. _`Django REST framework`: http://django-rest-framework.org
|
||||
.. _`Flask-Dance`: https://github.com/singingwolfboy/flask-dance
|
||||
.. _`pyramid-oauthlib`: https://github.com/tilgovi/pyramid-oauthlib
|
||||
.. _`bottle-oauthlib`: https://github.com/thomsonreuters/bottle-oauthlib
|
||||
|
||||
Using OAuthLib? Please get in touch!
|
||||
------------------------------------
|
||||
Patching OAuth support onto an http request framework? Creating an OAuth
|
||||
provider extension for a web framework? Simply using OAuthLib to Get Things Done
|
||||
or to learn?
|
||||
|
||||
No matter which we'd love to hear from you in our `Gitter community`_ or if you have
|
||||
anything in particular you would like to have, change or comment on don't
|
||||
hesitate for a second to send a pull request or open an issue. We might be quite
|
||||
busy and therefore slow to reply but we love feedback!
|
||||
|
||||
Chances are you have run into something annoying that you wish there was
|
||||
documentation for, if you wish to gain eternal fame and glory, and a drink if we
|
||||
have the pleasure to run into eachother, please send a docs pull request =)
|
||||
|
||||
.. _`Gitter community`: https://gitter.im/oauthlib/Lobby
|
||||
|
||||
License
|
||||
-------
|
||||
|
||||
OAuthLib is yours to use and abuse according to the terms of the BSD license.
|
||||
Check the LICENSE file for full details.
|
||||
|
||||
Credits
|
||||
-------
|
||||
|
||||
OAuthLib has been started and maintained several years by Idan Gazit and other
|
||||
amazing `AUTHORS`_. Thanks to their wonderful work, the open-source `community`_
|
||||
creation has been possible and the project can stay active and reactive to users
|
||||
requests.
|
||||
|
||||
|
||||
.. _`AUTHORS`: https://github.com/oauthlib/oauthlib/blob/master/AUTHORS
|
||||
.. _`community`: https://github.com/oauthlib/
|
||||
|
||||
Changelog
|
||||
---------
|
||||
|
||||
*OAuthLib is in active development, with the core of both OAuth1 and OAuth2
|
||||
completed, for providers as well as clients.* See `supported features`_ for
|
||||
details.
|
||||
|
||||
.. _`supported features`: https://oauthlib.readthedocs.io/en/latest/feature_matrix.html
|
||||
|
||||
For a full changelog see ``CHANGELOG.rst``.
|
||||
|
||||
Platform: any
|
||||
Classifier: Development Status :: 5 - Production/Stable
|
||||
Classifier: Environment :: Web Environment
|
||||
|
@ -150,18 +18,158 @@ Classifier: Operating System :: MacOS
|
|||
Classifier: Operating System :: POSIX
|
||||
Classifier: Operating System :: POSIX :: Linux
|
||||
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 :: 3.10
|
||||
Classifier: Programming Language :: Python :: 3 :: Only
|
||||
Classifier: Programming Language :: Python :: Implementation
|
||||
Classifier: Programming Language :: Python :: Implementation :: CPython
|
||||
Classifier: Programming Language :: Python :: Implementation :: PyPy
|
||||
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
||||
Requires-Python: >=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*
|
||||
Requires-Python: >=3.6
|
||||
Description-Content-Type: text/x-rst
|
||||
Provides-Extra: rsa
|
||||
Provides-Extra: signedtoken
|
||||
Provides-Extra: signals
|
||||
Provides-Extra: rsa
|
||||
License-File: LICENSE
|
||||
|
||||
OAuthLib - Python Framework for OAuth1 & OAuth2
|
||||
===============================================
|
||||
|
||||
*A generic, spec-compliant, thorough implementation of the OAuth request-signing
|
||||
logic for Python 3.6+.*
|
||||
|
||||
.. image:: https://app.travis-ci.com/oauthlib/oauthlib.svg?branch=master
|
||||
:target: https://app.travis-ci.com/oauthlib/oauthlib
|
||||
:alt: Travis
|
||||
.. image:: https://coveralls.io/repos/oauthlib/oauthlib/badge.svg?branch=master
|
||||
:target: https://coveralls.io/r/oauthlib/oauthlib
|
||||
:alt: Coveralls
|
||||
.. image:: https://img.shields.io/pypi/pyversions/oauthlib.svg
|
||||
:target: https://pypi.org/project/oauthlib/
|
||||
:alt: Download from PyPI
|
||||
.. image:: https://img.shields.io/pypi/l/oauthlib.svg
|
||||
:target: https://pypi.org/project/oauthlib/
|
||||
:alt: License
|
||||
.. image:: https://app.fossa.io/api/projects/git%2Bgithub.com%2Foauthlib%2Foauthlib.svg?type=shield
|
||||
:target: https://app.fossa.io/projects/git%2Bgithub.com%2Foauthlib%2Foauthlib?ref=badge_shield
|
||||
:alt: FOSSA Status
|
||||
.. image:: https://img.shields.io/readthedocs/oauthlib.svg
|
||||
:target: https://oauthlib.readthedocs.io/en/latest/index.html
|
||||
:alt: Read the Docs
|
||||
.. image:: https://badges.gitter.im/oauthlib/oauthlib.svg
|
||||
:target: https://gitter.im/oauthlib/Lobby
|
||||
:alt: Chat on Gitter
|
||||
|
||||
|
||||
.. image:: https://raw.githubusercontent.com/oauthlib/oauthlib/8d71b161fd145d11c40d55c9ab66ac134a303253/docs/logo/oauthlib-banner-700x192.png
|
||||
:target: https://github.com/oauthlib/oauthlib/
|
||||
:alt: OAuth + Python = OAuthlib Python Framework
|
||||
|
||||
|
||||
OAuth often seems complicated and difficult-to-implement. There are several
|
||||
prominent libraries for handling OAuth requests, but they all suffer from one or
|
||||
both of the following:
|
||||
|
||||
1. They predate the `OAuth 1.0 spec`_, AKA RFC 5849.
|
||||
2. They predate the `OAuth 2.0 spec`_, AKA RFC 6749.
|
||||
3. They assume the usage of a specific HTTP request library.
|
||||
|
||||
.. _`OAuth 1.0 spec`: https://tools.ietf.org/html/rfc5849
|
||||
.. _`OAuth 2.0 spec`: https://tools.ietf.org/html/rfc6749
|
||||
|
||||
OAuthLib is a framework which implements the logic of OAuth1 or OAuth2 without
|
||||
assuming a specific HTTP request object or web framework. Use it to graft OAuth
|
||||
client support onto your favorite HTTP library, or provide support onto your
|
||||
favourite web framework. If you're a maintainer of such a library, write a thin
|
||||
veneer on top of OAuthLib and get OAuth support for very little effort.
|
||||
|
||||
|
||||
Documentation
|
||||
--------------
|
||||
|
||||
Full documentation is available on `Read the Docs`_. All contributions are very
|
||||
welcome! The documentation is still quite sparse, please open an issue for what
|
||||
you'd like to know, or discuss it in our `Gitter community`_, or even better, send a
|
||||
pull request!
|
||||
|
||||
.. _`Gitter community`: https://gitter.im/oauthlib/Lobby
|
||||
.. _`Read the Docs`: https://oauthlib.readthedocs.io/en/latest/index.html
|
||||
|
||||
Interested in making OAuth requests?
|
||||
------------------------------------
|
||||
|
||||
Then you might be more interested in using `requests`_ which has OAuthLib
|
||||
powered OAuth support provided by the `requests-oauthlib`_ library.
|
||||
|
||||
.. _`requests`: https://github.com/requests/requests
|
||||
.. _`requests-oauthlib`: https://github.com/requests/requests-oauthlib
|
||||
|
||||
Which web frameworks are supported?
|
||||
-----------------------------------
|
||||
|
||||
The following packages provide OAuth support using OAuthLib.
|
||||
|
||||
- For Django there is `django-oauth-toolkit`_, which includes `Django REST framework`_ support.
|
||||
- For Flask there is `flask-oauthlib`_ and `Flask-Dance`_.
|
||||
- For Pyramid there is `pyramid-oauthlib`_.
|
||||
- For Bottle there is `bottle-oauthlib`_.
|
||||
|
||||
If you have written an OAuthLib package that supports your favorite framework,
|
||||
please open a Pull Request, updating the documentation.
|
||||
|
||||
.. _`django-oauth-toolkit`: https://github.com/evonove/django-oauth-toolkit
|
||||
.. _`flask-oauthlib`: https://github.com/lepture/flask-oauthlib
|
||||
.. _`Django REST framework`: http://django-rest-framework.org
|
||||
.. _`Flask-Dance`: https://github.com/singingwolfboy/flask-dance
|
||||
.. _`pyramid-oauthlib`: https://github.com/tilgovi/pyramid-oauthlib
|
||||
.. _`bottle-oauthlib`: https://github.com/thomsonreuters/bottle-oauthlib
|
||||
|
||||
Using OAuthLib? Please get in touch!
|
||||
------------------------------------
|
||||
Patching OAuth support onto an http request framework? Creating an OAuth
|
||||
provider extension for a web framework? Simply using OAuthLib to Get Things Done
|
||||
or to learn?
|
||||
|
||||
No matter which we'd love to hear from you in our `Gitter community`_ or if you have
|
||||
anything in particular you would like to have, change or comment on don't
|
||||
hesitate for a second to send a pull request or open an issue. We might be quite
|
||||
busy and therefore slow to reply but we love feedback!
|
||||
|
||||
Chances are you have run into something annoying that you wish there was
|
||||
documentation for, if you wish to gain eternal fame and glory, and a drink if we
|
||||
have the pleasure to run into each other, please send a docs pull request =)
|
||||
|
||||
.. _`Gitter community`: https://gitter.im/oauthlib/Lobby
|
||||
|
||||
License
|
||||
-------
|
||||
|
||||
OAuthLib is yours to use and abuse according to the terms of the BSD license.
|
||||
Check the LICENSE file for full details.
|
||||
|
||||
Credits
|
||||
-------
|
||||
|
||||
OAuthLib has been started and maintained several years by Idan Gazit and other
|
||||
amazing `AUTHORS`_. Thanks to their wonderful work, the open-source `community`_
|
||||
creation has been possible and the project can stay active and reactive to users
|
||||
requests.
|
||||
|
||||
|
||||
.. _`AUTHORS`: https://github.com/oauthlib/oauthlib/blob/master/AUTHORS
|
||||
.. _`community`: https://github.com/oauthlib/
|
||||
|
||||
Changelog
|
||||
---------
|
||||
|
||||
*OAuthLib is in active development, with the core of both OAuth1 and OAuth2
|
||||
completed, for providers as well as clients.* See `supported features`_ for
|
||||
details.
|
||||
|
||||
.. _`supported features`: https://oauthlib.readthedocs.io/en/latest/feature_matrix.html
|
||||
|
||||
For a full changelog see ``CHANGELOG.rst``.
|
||||
|
|
14
README.rst
14
README.rst
|
@ -2,10 +2,10 @@ OAuthLib - Python Framework for OAuth1 & OAuth2
|
|||
===============================================
|
||||
|
||||
*A generic, spec-compliant, thorough implementation of the OAuth request-signing
|
||||
logic for Python 2.7 and 3.4+.*
|
||||
logic for Python 3.6+.*
|
||||
|
||||
.. image:: https://travis-ci.org/oauthlib/oauthlib.svg?branch=master
|
||||
:target: https://travis-ci.org/oauthlib/oauthlib
|
||||
.. image:: https://app.travis-ci.com/oauthlib/oauthlib.svg?branch=master
|
||||
:target: https://app.travis-ci.com/oauthlib/oauthlib
|
||||
:alt: Travis
|
||||
.. image:: https://coveralls.io/repos/oauthlib/oauthlib/badge.svg?branch=master
|
||||
:target: https://coveralls.io/r/oauthlib/oauthlib
|
||||
|
@ -26,6 +26,12 @@ logic for Python 2.7 and 3.4+.*
|
|||
:target: https://gitter.im/oauthlib/Lobby
|
||||
:alt: Chat on Gitter
|
||||
|
||||
|
||||
.. image:: https://raw.githubusercontent.com/oauthlib/oauthlib/8d71b161fd145d11c40d55c9ab66ac134a303253/docs/logo/oauthlib-banner-700x192.png
|
||||
:target: https://github.com/oauthlib/oauthlib/
|
||||
:alt: OAuth + Python = OAuthlib Python Framework
|
||||
|
||||
|
||||
OAuth often seems complicated and difficult-to-implement. There are several
|
||||
prominent libraries for handling OAuth requests, but they all suffer from one or
|
||||
both of the following:
|
||||
|
@ -97,7 +103,7 @@ busy and therefore slow to reply but we love feedback!
|
|||
|
||||
Chances are you have run into something annoying that you wish there was
|
||||
documentation for, if you wish to gain eternal fame and glory, and a drink if we
|
||||
have the pleasure to run into eachother, please send a docs pull request =)
|
||||
have the pleasure to run into each other, please send a docs pull request =)
|
||||
|
||||
.. _`Gitter community`: https://gitter.im/oauthlib/Lobby
|
||||
|
||||
|
|
|
@ -1,11 +0,0 @@
|
|||
python-oauthlib (3.1.0-ok2) yangtze; urgency=medium
|
||||
|
||||
* update version info
|
||||
|
||||
-- luzhiping <luzhiping@kylinos.cn> Mon, 22 Aug 2022 14:19:08 +0800
|
||||
|
||||
python-oauthlib (3.1.0-ok1) yangtze; urgency=medium
|
||||
|
||||
* Build for openKylin.
|
||||
|
||||
-- openKylinBot <openKylinBot@openkylin.com> Mon, 25 Apr 2022 22:03:04 +0800
|
|
@ -1 +0,0 @@
|
|||
oauthlib.egg-info/*
|
|
@ -1,50 +0,0 @@
|
|||
Source: python-oauthlib
|
||||
Maintainer: Openkylin Developers <packaging@lists.openkylin.top>
|
||||
XSBC-Original-Maintainer: Debian Python Modules Team <python-modules-team@lists.alioth.debian.org>
|
||||
Uploaders: Daniele Tricoli <eriol@debian.org>
|
||||
Section: python
|
||||
Priority: optional
|
||||
Build-Depends:
|
||||
debhelper-compat (= 12),
|
||||
dh-python,
|
||||
python3-all,
|
||||
python3-blinker,
|
||||
python3-cryptography,
|
||||
python3-jwt (>= 1.0.0),
|
||||
python3-mock,
|
||||
python3-pytest (>= 4.0),
|
||||
python3-setuptools
|
||||
Standards-Version: 4.4.1
|
||||
Rules-Requires-Root: no
|
||||
Homepage: https://github.com/idan/oauthlib
|
||||
Vcs-Git: https://salsa.debian.org/python-team/modules/python-oauthlib.git
|
||||
Vcs-Browser: https://salsa.debian.org/python-team/modules/python-oauthlib
|
||||
|
||||
Package: python3-oauthlib
|
||||
Architecture: all
|
||||
Depends:
|
||||
${misc:Depends},
|
||||
${python3:Depends},
|
||||
python3-blinker,
|
||||
python3-cryptography,
|
||||
python3-jwt (>= 1.0.0)
|
||||
Description: generic, spec-compliant implementation of OAuth for Python3
|
||||
OAuthLib is a generic utility which implements the logic of OAuth without
|
||||
assuming a specific HTTP request object. It can be used to graft OAuth support
|
||||
onto HTTP libraries.
|
||||
.
|
||||
OAuth 1 is fully supported per the RFC for both clients and providers.
|
||||
.
|
||||
OAuth 2 client and provider support for:
|
||||
.
|
||||
- Authorization Code Grant
|
||||
- Implicit Grant
|
||||
- Client Credentials Grant
|
||||
- Resource Owner Password Credentials Grant
|
||||
- Refresh Tokens
|
||||
- Bearer Tokens
|
||||
- Draft MAC tokens
|
||||
- Token Revocation
|
||||
- OpenID Connect Authentication
|
||||
.
|
||||
This package contains the Python 3 version of the library.
|
|
@ -1,40 +0,0 @@
|
|||
Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
|
||||
Upstream-Name: oauthlib
|
||||
Upstream-Contact: Idan Gazit <idan@gazit.me>
|
||||
Source: http://pypi.python.org/pypi/oauthlib
|
||||
|
||||
Files: *
|
||||
Copyright: 2011, Idan Gazit and contributors
|
||||
License: BSD-3-clause
|
||||
|
||||
Files: debian/*
|
||||
Copyright: 2012-2019 Daniele Tricoli <eriol@debian.org>
|
||||
License: BSD-3-clause
|
||||
|
||||
License: BSD-3-clause
|
||||
All rights reserved.
|
||||
.
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
.
|
||||
1. Redistributions of source code must retain the above copyright notice,
|
||||
this list of conditions and the following disclaimer.
|
||||
.
|
||||
2. Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
.
|
||||
3. Neither the name of this project nor the names of its contributors may
|
||||
be used to endorse or promote products derived from this software
|
||||
without specific prior written permission.
|
||||
.
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
@ -1,2 +0,0 @@
|
|||
README.rst
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
#!/usr/bin/make -f
|
||||
|
||||
export PYBUILD_NAME=oauthlib
|
||||
|
||||
export PYTHONWARNINGS=d
|
||||
|
||||
%:
|
||||
dh $@ --with python3 --buildsystem=pybuild
|
|
@ -1 +0,0 @@
|
|||
3.0 (native)
|
|
@ -1,6 +0,0 @@
|
|||
Tests: python3-oauthlib
|
||||
Depends:
|
||||
python3-all,
|
||||
python3-mock,
|
||||
python3-pytest (>= 4.0),
|
||||
python3-oauthlib
|
|
@ -1,6 +0,0 @@
|
|||
#!/bin/sh -e
|
||||
|
||||
cp -r tests "$AUTOPKGTEST_TMP"
|
||||
cd "$AUTOPKGTEST_TMP"
|
||||
|
||||
py3versions -i | tr ' ' '\n' | xargs -I {} env {} -Wd -m pytest -v -x -rs 2>&1
|
|
@ -1,3 +0,0 @@
|
|||
version=3
|
||||
opts=uversionmangle=s/(rc|a|b|c)/~$1/ \
|
||||
https://pypi.debian.net/oauthlib/oauthlib-(.+)\.(?:zip|tgz|tbz|txz|(?:tar\.(?:gz|bz2|xz)))
|
|
@ -1,6 +1,6 @@
|
|||
Metadata-Version: 2.1
|
||||
Name: oauthlib
|
||||
Version: 3.1.0
|
||||
Version: 3.2.2
|
||||
Summary: A generic, spec-compliant, thorough implementation of the OAuth request-signing logic
|
||||
Home-page: https://github.com/oauthlib/oauthlib
|
||||
Author: The OAuthlib Community
|
||||
|
@ -8,138 +8,6 @@ Author-email: idan@gazit.me
|
|||
Maintainer: Ib Lundgren
|
||||
Maintainer-email: ib.lundgren@gmail.com
|
||||
License: BSD
|
||||
Description: OAuthLib - Python Framework for OAuth1 & OAuth2
|
||||
===============================================
|
||||
|
||||
*A generic, spec-compliant, thorough implementation of the OAuth request-signing
|
||||
logic for Python 2.7 and 3.4+.*
|
||||
|
||||
.. image:: https://travis-ci.org/oauthlib/oauthlib.svg?branch=master
|
||||
:target: https://travis-ci.org/oauthlib/oauthlib
|
||||
:alt: Travis
|
||||
.. image:: https://coveralls.io/repos/oauthlib/oauthlib/badge.svg?branch=master
|
||||
:target: https://coveralls.io/r/oauthlib/oauthlib
|
||||
:alt: Coveralls
|
||||
.. image:: https://img.shields.io/pypi/pyversions/oauthlib.svg
|
||||
:target: https://pypi.org/project/oauthlib/
|
||||
:alt: Download from PyPI
|
||||
.. image:: https://img.shields.io/pypi/l/oauthlib.svg
|
||||
:target: https://pypi.org/project/oauthlib/
|
||||
:alt: License
|
||||
.. image:: https://app.fossa.io/api/projects/git%2Bgithub.com%2Foauthlib%2Foauthlib.svg?type=shield
|
||||
:target: https://app.fossa.io/projects/git%2Bgithub.com%2Foauthlib%2Foauthlib?ref=badge_shield
|
||||
:alt: FOSSA Status
|
||||
.. image:: https://img.shields.io/readthedocs/oauthlib.svg
|
||||
:target: https://oauthlib.readthedocs.io/en/latest/index.html
|
||||
:alt: Read the Docs
|
||||
.. image:: https://badges.gitter.im/oauthlib/oauthlib.svg
|
||||
:target: https://gitter.im/oauthlib/Lobby
|
||||
:alt: Chat on Gitter
|
||||
|
||||
OAuth often seems complicated and difficult-to-implement. There are several
|
||||
prominent libraries for handling OAuth requests, but they all suffer from one or
|
||||
both of the following:
|
||||
|
||||
1. They predate the `OAuth 1.0 spec`_, AKA RFC 5849.
|
||||
2. They predate the `OAuth 2.0 spec`_, AKA RFC 6749.
|
||||
3. They assume the usage of a specific HTTP request library.
|
||||
|
||||
.. _`OAuth 1.0 spec`: https://tools.ietf.org/html/rfc5849
|
||||
.. _`OAuth 2.0 spec`: https://tools.ietf.org/html/rfc6749
|
||||
|
||||
OAuthLib is a framework which implements the logic of OAuth1 or OAuth2 without
|
||||
assuming a specific HTTP request object or web framework. Use it to graft OAuth
|
||||
client support onto your favorite HTTP library, or provide support onto your
|
||||
favourite web framework. If you're a maintainer of such a library, write a thin
|
||||
veneer on top of OAuthLib and get OAuth support for very little effort.
|
||||
|
||||
|
||||
Documentation
|
||||
--------------
|
||||
|
||||
Full documentation is available on `Read the Docs`_. All contributions are very
|
||||
welcome! The documentation is still quite sparse, please open an issue for what
|
||||
you'd like to know, or discuss it in our `Gitter community`_, or even better, send a
|
||||
pull request!
|
||||
|
||||
.. _`Gitter community`: https://gitter.im/oauthlib/Lobby
|
||||
.. _`Read the Docs`: https://oauthlib.readthedocs.io/en/latest/index.html
|
||||
|
||||
Interested in making OAuth requests?
|
||||
------------------------------------
|
||||
|
||||
Then you might be more interested in using `requests`_ which has OAuthLib
|
||||
powered OAuth support provided by the `requests-oauthlib`_ library.
|
||||
|
||||
.. _`requests`: https://github.com/requests/requests
|
||||
.. _`requests-oauthlib`: https://github.com/requests/requests-oauthlib
|
||||
|
||||
Which web frameworks are supported?
|
||||
-----------------------------------
|
||||
|
||||
The following packages provide OAuth support using OAuthLib.
|
||||
|
||||
- For Django there is `django-oauth-toolkit`_, which includes `Django REST framework`_ support.
|
||||
- For Flask there is `flask-oauthlib`_ and `Flask-Dance`_.
|
||||
- For Pyramid there is `pyramid-oauthlib`_.
|
||||
- For Bottle there is `bottle-oauthlib`_.
|
||||
|
||||
If you have written an OAuthLib package that supports your favorite framework,
|
||||
please open a Pull Request, updating the documentation.
|
||||
|
||||
.. _`django-oauth-toolkit`: https://github.com/evonove/django-oauth-toolkit
|
||||
.. _`flask-oauthlib`: https://github.com/lepture/flask-oauthlib
|
||||
.. _`Django REST framework`: http://django-rest-framework.org
|
||||
.. _`Flask-Dance`: https://github.com/singingwolfboy/flask-dance
|
||||
.. _`pyramid-oauthlib`: https://github.com/tilgovi/pyramid-oauthlib
|
||||
.. _`bottle-oauthlib`: https://github.com/thomsonreuters/bottle-oauthlib
|
||||
|
||||
Using OAuthLib? Please get in touch!
|
||||
------------------------------------
|
||||
Patching OAuth support onto an http request framework? Creating an OAuth
|
||||
provider extension for a web framework? Simply using OAuthLib to Get Things Done
|
||||
or to learn?
|
||||
|
||||
No matter which we'd love to hear from you in our `Gitter community`_ or if you have
|
||||
anything in particular you would like to have, change or comment on don't
|
||||
hesitate for a second to send a pull request or open an issue. We might be quite
|
||||
busy and therefore slow to reply but we love feedback!
|
||||
|
||||
Chances are you have run into something annoying that you wish there was
|
||||
documentation for, if you wish to gain eternal fame and glory, and a drink if we
|
||||
have the pleasure to run into eachother, please send a docs pull request =)
|
||||
|
||||
.. _`Gitter community`: https://gitter.im/oauthlib/Lobby
|
||||
|
||||
License
|
||||
-------
|
||||
|
||||
OAuthLib is yours to use and abuse according to the terms of the BSD license.
|
||||
Check the LICENSE file for full details.
|
||||
|
||||
Credits
|
||||
-------
|
||||
|
||||
OAuthLib has been started and maintained several years by Idan Gazit and other
|
||||
amazing `AUTHORS`_. Thanks to their wonderful work, the open-source `community`_
|
||||
creation has been possible and the project can stay active and reactive to users
|
||||
requests.
|
||||
|
||||
|
||||
.. _`AUTHORS`: https://github.com/oauthlib/oauthlib/blob/master/AUTHORS
|
||||
.. _`community`: https://github.com/oauthlib/
|
||||
|
||||
Changelog
|
||||
---------
|
||||
|
||||
*OAuthLib is in active development, with the core of both OAuth1 and OAuth2
|
||||
completed, for providers as well as clients.* See `supported features`_ for
|
||||
details.
|
||||
|
||||
.. _`supported features`: https://oauthlib.readthedocs.io/en/latest/feature_matrix.html
|
||||
|
||||
For a full changelog see ``CHANGELOG.rst``.
|
||||
|
||||
Platform: any
|
||||
Classifier: Development Status :: 5 - Production/Stable
|
||||
Classifier: Environment :: Web Environment
|
||||
|
@ -150,18 +18,158 @@ Classifier: Operating System :: MacOS
|
|||
Classifier: Operating System :: POSIX
|
||||
Classifier: Operating System :: POSIX :: Linux
|
||||
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 :: 3.10
|
||||
Classifier: Programming Language :: Python :: 3 :: Only
|
||||
Classifier: Programming Language :: Python :: Implementation
|
||||
Classifier: Programming Language :: Python :: Implementation :: CPython
|
||||
Classifier: Programming Language :: Python :: Implementation :: PyPy
|
||||
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
||||
Requires-Python: >=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*
|
||||
Requires-Python: >=3.6
|
||||
Description-Content-Type: text/x-rst
|
||||
Provides-Extra: rsa
|
||||
Provides-Extra: signedtoken
|
||||
Provides-Extra: signals
|
||||
Provides-Extra: rsa
|
||||
License-File: LICENSE
|
||||
|
||||
OAuthLib - Python Framework for OAuth1 & OAuth2
|
||||
===============================================
|
||||
|
||||
*A generic, spec-compliant, thorough implementation of the OAuth request-signing
|
||||
logic for Python 3.6+.*
|
||||
|
||||
.. image:: https://app.travis-ci.com/oauthlib/oauthlib.svg?branch=master
|
||||
:target: https://app.travis-ci.com/oauthlib/oauthlib
|
||||
:alt: Travis
|
||||
.. image:: https://coveralls.io/repos/oauthlib/oauthlib/badge.svg?branch=master
|
||||
:target: https://coveralls.io/r/oauthlib/oauthlib
|
||||
:alt: Coveralls
|
||||
.. image:: https://img.shields.io/pypi/pyversions/oauthlib.svg
|
||||
:target: https://pypi.org/project/oauthlib/
|
||||
:alt: Download from PyPI
|
||||
.. image:: https://img.shields.io/pypi/l/oauthlib.svg
|
||||
:target: https://pypi.org/project/oauthlib/
|
||||
:alt: License
|
||||
.. image:: https://app.fossa.io/api/projects/git%2Bgithub.com%2Foauthlib%2Foauthlib.svg?type=shield
|
||||
:target: https://app.fossa.io/projects/git%2Bgithub.com%2Foauthlib%2Foauthlib?ref=badge_shield
|
||||
:alt: FOSSA Status
|
||||
.. image:: https://img.shields.io/readthedocs/oauthlib.svg
|
||||
:target: https://oauthlib.readthedocs.io/en/latest/index.html
|
||||
:alt: Read the Docs
|
||||
.. image:: https://badges.gitter.im/oauthlib/oauthlib.svg
|
||||
:target: https://gitter.im/oauthlib/Lobby
|
||||
:alt: Chat on Gitter
|
||||
|
||||
|
||||
.. image:: https://raw.githubusercontent.com/oauthlib/oauthlib/8d71b161fd145d11c40d55c9ab66ac134a303253/docs/logo/oauthlib-banner-700x192.png
|
||||
:target: https://github.com/oauthlib/oauthlib/
|
||||
:alt: OAuth + Python = OAuthlib Python Framework
|
||||
|
||||
|
||||
OAuth often seems complicated and difficult-to-implement. There are several
|
||||
prominent libraries for handling OAuth requests, but they all suffer from one or
|
||||
both of the following:
|
||||
|
||||
1. They predate the `OAuth 1.0 spec`_, AKA RFC 5849.
|
||||
2. They predate the `OAuth 2.0 spec`_, AKA RFC 6749.
|
||||
3. They assume the usage of a specific HTTP request library.
|
||||
|
||||
.. _`OAuth 1.0 spec`: https://tools.ietf.org/html/rfc5849
|
||||
.. _`OAuth 2.0 spec`: https://tools.ietf.org/html/rfc6749
|
||||
|
||||
OAuthLib is a framework which implements the logic of OAuth1 or OAuth2 without
|
||||
assuming a specific HTTP request object or web framework. Use it to graft OAuth
|
||||
client support onto your favorite HTTP library, or provide support onto your
|
||||
favourite web framework. If you're a maintainer of such a library, write a thin
|
||||
veneer on top of OAuthLib and get OAuth support for very little effort.
|
||||
|
||||
|
||||
Documentation
|
||||
--------------
|
||||
|
||||
Full documentation is available on `Read the Docs`_. All contributions are very
|
||||
welcome! The documentation is still quite sparse, please open an issue for what
|
||||
you'd like to know, or discuss it in our `Gitter community`_, or even better, send a
|
||||
pull request!
|
||||
|
||||
.. _`Gitter community`: https://gitter.im/oauthlib/Lobby
|
||||
.. _`Read the Docs`: https://oauthlib.readthedocs.io/en/latest/index.html
|
||||
|
||||
Interested in making OAuth requests?
|
||||
------------------------------------
|
||||
|
||||
Then you might be more interested in using `requests`_ which has OAuthLib
|
||||
powered OAuth support provided by the `requests-oauthlib`_ library.
|
||||
|
||||
.. _`requests`: https://github.com/requests/requests
|
||||
.. _`requests-oauthlib`: https://github.com/requests/requests-oauthlib
|
||||
|
||||
Which web frameworks are supported?
|
||||
-----------------------------------
|
||||
|
||||
The following packages provide OAuth support using OAuthLib.
|
||||
|
||||
- For Django there is `django-oauth-toolkit`_, which includes `Django REST framework`_ support.
|
||||
- For Flask there is `flask-oauthlib`_ and `Flask-Dance`_.
|
||||
- For Pyramid there is `pyramid-oauthlib`_.
|
||||
- For Bottle there is `bottle-oauthlib`_.
|
||||
|
||||
If you have written an OAuthLib package that supports your favorite framework,
|
||||
please open a Pull Request, updating the documentation.
|
||||
|
||||
.. _`django-oauth-toolkit`: https://github.com/evonove/django-oauth-toolkit
|
||||
.. _`flask-oauthlib`: https://github.com/lepture/flask-oauthlib
|
||||
.. _`Django REST framework`: http://django-rest-framework.org
|
||||
.. _`Flask-Dance`: https://github.com/singingwolfboy/flask-dance
|
||||
.. _`pyramid-oauthlib`: https://github.com/tilgovi/pyramid-oauthlib
|
||||
.. _`bottle-oauthlib`: https://github.com/thomsonreuters/bottle-oauthlib
|
||||
|
||||
Using OAuthLib? Please get in touch!
|
||||
------------------------------------
|
||||
Patching OAuth support onto an http request framework? Creating an OAuth
|
||||
provider extension for a web framework? Simply using OAuthLib to Get Things Done
|
||||
or to learn?
|
||||
|
||||
No matter which we'd love to hear from you in our `Gitter community`_ or if you have
|
||||
anything in particular you would like to have, change or comment on don't
|
||||
hesitate for a second to send a pull request or open an issue. We might be quite
|
||||
busy and therefore slow to reply but we love feedback!
|
||||
|
||||
Chances are you have run into something annoying that you wish there was
|
||||
documentation for, if you wish to gain eternal fame and glory, and a drink if we
|
||||
have the pleasure to run into each other, please send a docs pull request =)
|
||||
|
||||
.. _`Gitter community`: https://gitter.im/oauthlib/Lobby
|
||||
|
||||
License
|
||||
-------
|
||||
|
||||
OAuthLib is yours to use and abuse according to the terms of the BSD license.
|
||||
Check the LICENSE file for full details.
|
||||
|
||||
Credits
|
||||
-------
|
||||
|
||||
OAuthLib has been started and maintained several years by Idan Gazit and other
|
||||
amazing `AUTHORS`_. Thanks to their wonderful work, the open-source `community`_
|
||||
creation has been possible and the project can stay active and reactive to users
|
||||
requests.
|
||||
|
||||
|
||||
.. _`AUTHORS`: https://github.com/oauthlib/oauthlib/blob/master/AUTHORS
|
||||
.. _`community`: https://github.com/oauthlib/
|
||||
|
||||
Changelog
|
||||
---------
|
||||
|
||||
*OAuthLib is in active development, with the core of both OAuth1 and OAuth2
|
||||
completed, for providers as well as clients.* See `supported features`_ for
|
||||
details.
|
||||
|
||||
.. _`supported features`: https://oauthlib.readthedocs.io/en/latest/feature_matrix.html
|
||||
|
||||
For a full changelog see ``CHANGELOG.rst``.
|
||||
|
|
|
@ -58,6 +58,9 @@ oauthlib/oauth2/rfc6749/grant_types/client_credentials.py
|
|||
oauthlib/oauth2/rfc6749/grant_types/implicit.py
|
||||
oauthlib/oauth2/rfc6749/grant_types/refresh_token.py
|
||||
oauthlib/oauth2/rfc6749/grant_types/resource_owner_password_credentials.py
|
||||
oauthlib/oauth2/rfc8628/__init__.py
|
||||
oauthlib/oauth2/rfc8628/clients/__init__.py
|
||||
oauthlib/oauth2/rfc8628/clients/device.py
|
||||
oauthlib/openid/__init__.py
|
||||
oauthlib/openid/connect/__init__.py
|
||||
oauthlib/openid/connect/core/__init__.py
|
||||
|
@ -71,11 +74,12 @@ oauthlib/openid/connect/core/grant_types/__init__.py
|
|||
oauthlib/openid/connect/core/grant_types/authorization_code.py
|
||||
oauthlib/openid/connect/core/grant_types/base.py
|
||||
oauthlib/openid/connect/core/grant_types/dispatchers.py
|
||||
oauthlib/openid/connect/core/grant_types/exceptions.py
|
||||
oauthlib/openid/connect/core/grant_types/hybrid.py
|
||||
oauthlib/openid/connect/core/grant_types/implicit.py
|
||||
oauthlib/openid/connect/core/grant_types/refresh_token.py
|
||||
tests/__init__.py
|
||||
tests/test_common.py
|
||||
tests/test_uri_validate.py
|
||||
tests/oauth1/__init__.py
|
||||
tests/oauth1/rfc5849/__init__.py
|
||||
tests/oauth1/rfc5849/test_client.py
|
||||
|
@ -122,6 +126,9 @@ tests/oauth2/rfc6749/grant_types/test_client_credentials.py
|
|||
tests/oauth2/rfc6749/grant_types/test_implicit.py
|
||||
tests/oauth2/rfc6749/grant_types/test_refresh_token.py
|
||||
tests/oauth2/rfc6749/grant_types/test_resource_owner_password.py
|
||||
tests/oauth2/rfc8628/__init__.py
|
||||
tests/oauth2/rfc8628/clients/__init__.py
|
||||
tests/oauth2/rfc8628/clients/test_device.py
|
||||
tests/openid/__init__.py
|
||||
tests/openid/connect/__init__.py
|
||||
tests/openid/connect/core/__init__.py
|
||||
|
@ -138,4 +145,5 @@ tests/openid/connect/core/grant_types/test_base.py
|
|||
tests/openid/connect/core/grant_types/test_dispatchers.py
|
||||
tests/openid/connect/core/grant_types/test_hybrid.py
|
||||
tests/openid/connect/core/grant_types/test_implicit.py
|
||||
tests/openid/connect/core/grant_types/test_refresh_token.py
|
||||
tests/unittest/__init__.py
|
|
@ -1,10 +1,10 @@
|
|||
|
||||
[rsa]
|
||||
cryptography
|
||||
cryptography>=3.0.0
|
||||
|
||||
[signals]
|
||||
blinker
|
||||
blinker>=1.4.0
|
||||
|
||||
[signedtoken]
|
||||
cryptography
|
||||
pyjwt>=1.0.0
|
||||
cryptography>=3.0.0
|
||||
pyjwt<3,>=2.0.0
|
||||
|
|
|
@ -12,7 +12,7 @@ import logging
|
|||
from logging import NullHandler
|
||||
|
||||
__author__ = 'The OAuthlib Community'
|
||||
__version__ = '3.1.0'
|
||||
__version__ = '3.2.2'
|
||||
|
||||
logging.getLogger('oauthlib').addHandler(NullHandler())
|
||||
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
oauthlib.common
|
||||
~~~~~~~~~~~~~~
|
||||
|
@ -6,34 +5,22 @@ oauthlib.common
|
|||
This module provides data structures and utilities common
|
||||
to all implementations of OAuth.
|
||||
"""
|
||||
from __future__ import absolute_import, unicode_literals
|
||||
|
||||
import collections
|
||||
import datetime
|
||||
import logging
|
||||
import re
|
||||
import sys
|
||||
import time
|
||||
import urllib.parse as urlparse
|
||||
from urllib.parse import (
|
||||
quote as _quote, unquote as _unquote, urlencode as _urlencode,
|
||||
)
|
||||
|
||||
from . import get_debug
|
||||
|
||||
try:
|
||||
from secrets import randbits
|
||||
from secrets import SystemRandom
|
||||
from secrets import SystemRandom, randbits
|
||||
except ImportError:
|
||||
from random import getrandbits as randbits
|
||||
from random import SystemRandom
|
||||
try:
|
||||
from urllib import quote as _quote
|
||||
from urllib import unquote as _unquote
|
||||
from urllib import urlencode as _urlencode
|
||||
except ImportError:
|
||||
from urllib.parse import quote as _quote
|
||||
from urllib.parse import unquote as _unquote
|
||||
from urllib.parse import urlencode as _urlencode
|
||||
try:
|
||||
import urlparse
|
||||
except ImportError:
|
||||
import urllib.parse as urlparse
|
||||
from random import SystemRandom, getrandbits as randbits
|
||||
|
||||
UNICODE_ASCII_CHARACTER_SET = ('abcdefghijklmnopqrstuvwxyz'
|
||||
'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
|
||||
|
@ -51,17 +38,10 @@ always_safe = ('ABCDEFGHIJKLMNOPQRSTUVWXYZ'
|
|||
|
||||
log = logging.getLogger('oauthlib')
|
||||
|
||||
PY3 = sys.version_info[0] == 3
|
||||
|
||||
if PY3:
|
||||
unicode_type = str
|
||||
else:
|
||||
unicode_type = unicode
|
||||
|
||||
|
||||
# 'safe' must be bytes (Python 2.6 requires bytes, other versions allow either)
|
||||
def quote(s, safe=b'/'):
|
||||
s = s.encode('utf-8') if isinstance(s, unicode_type) else s
|
||||
s = s.encode('utf-8') if isinstance(s, str) else s
|
||||
s = _quote(s, safe)
|
||||
# PY3 always returns unicode. PY2 may return either, depending on whether
|
||||
# it had to modify the string.
|
||||
|
@ -83,7 +63,7 @@ def unquote(s):
|
|||
def urlencode(params):
|
||||
utf8_params = encode_params_utf8(params)
|
||||
urlencoded = _urlencode(utf8_params)
|
||||
if isinstance(urlencoded, unicode_type): # PY3 returns unicode
|
||||
if isinstance(urlencoded, str):
|
||||
return urlencoded
|
||||
else:
|
||||
return urlencoded.decode("utf-8")
|
||||
|
@ -96,8 +76,8 @@ def encode_params_utf8(params):
|
|||
encoded = []
|
||||
for k, v in params:
|
||||
encoded.append((
|
||||
k.encode('utf-8') if isinstance(k, unicode_type) else k,
|
||||
v.encode('utf-8') if isinstance(v, unicode_type) else v))
|
||||
k.encode('utf-8') if isinstance(k, str) else k,
|
||||
v.encode('utf-8') if isinstance(v, str) else v))
|
||||
return encoded
|
||||
|
||||
|
||||
|
@ -141,22 +121,6 @@ def urldecode(query):
|
|||
if INVALID_HEX_PATTERN.search(query):
|
||||
raise ValueError('Invalid hex encoding in query string.')
|
||||
|
||||
# We encode to utf-8 prior to parsing because parse_qsl behaves
|
||||
# differently on unicode input in python 2 and 3.
|
||||
# Python 2.7
|
||||
# >>> urlparse.parse_qsl(u'%E5%95%A6%E5%95%A6')
|
||||
# u'\xe5\x95\xa6\xe5\x95\xa6'
|
||||
# Python 2.7, non unicode input gives the same
|
||||
# >>> urlparse.parse_qsl('%E5%95%A6%E5%95%A6')
|
||||
# '\xe5\x95\xa6\xe5\x95\xa6'
|
||||
# but now we can decode it to unicode
|
||||
# >>> urlparse.parse_qsl('%E5%95%A6%E5%95%A6').decode('utf-8')
|
||||
# u'\u5566\u5566'
|
||||
# Python 3.3 however
|
||||
# >>> urllib.parse.parse_qsl(u'%E5%95%A6%E5%95%A6')
|
||||
# u'\u5566\u5566'
|
||||
query = query.encode(
|
||||
'utf-8') if not PY3 and isinstance(query, unicode_type) else query
|
||||
# We want to allow queries such as "c2" whereas urlparse.parse_qsl
|
||||
# with the strict_parsing flag will not.
|
||||
params = urlparse.parse_qsl(query, keep_blank_values=True)
|
||||
|
@ -173,7 +137,7 @@ def extract_params(raw):
|
|||
empty list of parameters. Any other input will result in a return
|
||||
value of None.
|
||||
"""
|
||||
if isinstance(raw, (bytes, unicode_type)):
|
||||
if isinstance(raw, (bytes, str)):
|
||||
try:
|
||||
params = urldecode(raw)
|
||||
except ValueError:
|
||||
|
@ -206,7 +170,7 @@ def generate_nonce():
|
|||
.. _`section 3.2.1`: https://tools.ietf.org/html/draft-ietf-oauth-v2-http-mac-01#section-3.2.1
|
||||
.. _`section 3.3`: https://tools.ietf.org/html/rfc5849#section-3.3
|
||||
"""
|
||||
return unicode_type(unicode_type(randbits(64)) + generate_timestamp())
|
||||
return str(str(randbits(64)) + generate_timestamp())
|
||||
|
||||
|
||||
def generate_timestamp():
|
||||
|
@ -218,7 +182,7 @@ def generate_timestamp():
|
|||
.. _`section 3.2.1`: https://tools.ietf.org/html/draft-ietf-oauth-v2-http-mac-01#section-3.2.1
|
||||
.. _`section 3.3`: https://tools.ietf.org/html/rfc5849#section-3.3
|
||||
"""
|
||||
return unicode_type(int(time.time()))
|
||||
return str(int(time.time()))
|
||||
|
||||
|
||||
def generate_token(length=30, chars=UNICODE_ASCII_CHARACTER_SET):
|
||||
|
@ -305,11 +269,11 @@ def safe_string_equals(a, b):
|
|||
|
||||
def to_unicode(data, encoding='UTF-8'):
|
||||
"""Convert a number of different types of objects to unicode."""
|
||||
if isinstance(data, unicode_type):
|
||||
if isinstance(data, str):
|
||||
return data
|
||||
|
||||
if isinstance(data, bytes):
|
||||
return unicode_type(data, encoding=encoding)
|
||||
return str(data, encoding=encoding)
|
||||
|
||||
if hasattr(data, '__iter__'):
|
||||
try:
|
||||
|
@ -323,7 +287,7 @@ def to_unicode(data, encoding='UTF-8'):
|
|||
# We support 2.6 which lacks dict comprehensions
|
||||
if hasattr(data, 'items'):
|
||||
data = data.items()
|
||||
return dict(((to_unicode(k, encoding), to_unicode(v, encoding)) for k, v in data))
|
||||
return {to_unicode(k, encoding): to_unicode(v, encoding) for k, v in data}
|
||||
|
||||
return data
|
||||
|
||||
|
@ -335,7 +299,7 @@ class CaseInsensitiveDict(dict):
|
|||
proxy = {}
|
||||
|
||||
def __init__(self, data):
|
||||
self.proxy = dict((k.lower(), k) for k in data)
|
||||
self.proxy = {k.lower(): k for k in data}
|
||||
for k in data:
|
||||
self[k] = data[k]
|
||||
|
||||
|
@ -344,27 +308,27 @@ class CaseInsensitiveDict(dict):
|
|||
|
||||
def __delitem__(self, k):
|
||||
key = self.proxy[k.lower()]
|
||||
super(CaseInsensitiveDict, self).__delitem__(key)
|
||||
super().__delitem__(key)
|
||||
del self.proxy[k.lower()]
|
||||
|
||||
def __getitem__(self, k):
|
||||
key = self.proxy[k.lower()]
|
||||
return super(CaseInsensitiveDict, self).__getitem__(key)
|
||||
return super().__getitem__(key)
|
||||
|
||||
def get(self, k, default=None):
|
||||
return self[k] if k in self else default
|
||||
|
||||
def __setitem__(self, k, v):
|
||||
super(CaseInsensitiveDict, self).__setitem__(k, v)
|
||||
super().__setitem__(k, v)
|
||||
self.proxy[k.lower()] = k
|
||||
|
||||
def update(self, *args, **kwargs):
|
||||
super(CaseInsensitiveDict, self).update(*args, **kwargs)
|
||||
super().update(*args, **kwargs)
|
||||
for k in dict(*args, **kwargs):
|
||||
self.proxy[k.lower()] = k
|
||||
|
||||
|
||||
class Request(object):
|
||||
class Request:
|
||||
|
||||
"""A malleable representation of a signable HTTP request.
|
||||
|
||||
|
@ -444,7 +408,7 @@ class Request(object):
|
|||
body = SANITIZE_PATTERN.sub('\1<SANITIZED>', str(body))
|
||||
if 'Authorization' in headers:
|
||||
headers['Authorization'] = '<SANITIZED>'
|
||||
return '<oauthlib.Request url="%s", http_method="%s", headers="%s", body="%s">' % (
|
||||
return '<oauthlib.Request url="{}", http_method="{}", headers="{}", body="{}">'.format(
|
||||
self.uri, self.http_method, headers, body)
|
||||
|
||||
@property
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
oauthlib.oauth1
|
||||
~~~~~~~~~~~~~~
|
||||
|
@ -6,14 +5,19 @@ oauthlib.oauth1
|
|||
This module is a wrapper for the most recent implementation of OAuth 1.0 Client
|
||||
and Server classes.
|
||||
"""
|
||||
from __future__ import absolute_import, unicode_literals
|
||||
|
||||
from .rfc5849 import Client
|
||||
from .rfc5849 import SIGNATURE_HMAC, SIGNATURE_HMAC_SHA1, SIGNATURE_HMAC_SHA256, SIGNATURE_RSA, SIGNATURE_PLAINTEXT
|
||||
from .rfc5849 import SIGNATURE_TYPE_AUTH_HEADER, SIGNATURE_TYPE_QUERY
|
||||
from .rfc5849 import SIGNATURE_TYPE_BODY
|
||||
from .rfc5849 import (
|
||||
SIGNATURE_HMAC, SIGNATURE_HMAC_SHA1, SIGNATURE_HMAC_SHA256,
|
||||
SIGNATURE_HMAC_SHA512, SIGNATURE_PLAINTEXT, SIGNATURE_RSA,
|
||||
SIGNATURE_RSA_SHA1, SIGNATURE_RSA_SHA256, SIGNATURE_RSA_SHA512,
|
||||
SIGNATURE_TYPE_AUTH_HEADER, SIGNATURE_TYPE_BODY, SIGNATURE_TYPE_QUERY,
|
||||
Client,
|
||||
)
|
||||
from .rfc5849.endpoints import (
|
||||
AccessTokenEndpoint, AuthorizationEndpoint, RequestTokenEndpoint,
|
||||
ResourceEndpoint, SignatureOnlyEndpoint, WebApplicationServer,
|
||||
)
|
||||
from .rfc5849.errors import (
|
||||
InsecureTransportError, InvalidClientError, InvalidRequestError,
|
||||
InvalidSignatureMethodError, OAuth1Error,
|
||||
)
|
||||
from .rfc5849.request_validator import RequestValidator
|
||||
from .rfc5849.endpoints import RequestTokenEndpoint, AuthorizationEndpoint
|
||||
from .rfc5849.endpoints import AccessTokenEndpoint, ResourceEndpoint
|
||||
from .rfc5849.endpoints import SignatureOnlyEndpoint, WebApplicationServer
|
||||
from .rfc5849.errors import InsecureTransportError, InvalidClientError, InvalidRequestError, InvalidSignatureMethodError, OAuth1Error
|
||||
|
|
|
@ -1,33 +1,68 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
oauthlib.oauth1.rfc5849
|
||||
~~~~~~~~~~~~~~
|
||||
|
||||
This module is an implementation of various logic needed
|
||||
for signing and checking OAuth 1.0 RFC 5849 requests.
|
||||
|
||||
It supports all three standard signature methods defined in RFC 5849:
|
||||
|
||||
- HMAC-SHA1
|
||||
- RSA-SHA1
|
||||
- PLAINTEXT
|
||||
|
||||
It also supports signature methods that are not defined in RFC 5849. These are
|
||||
based on the standard ones but replace SHA-1 with the more secure SHA-256:
|
||||
|
||||
- HMAC-SHA256
|
||||
- RSA-SHA256
|
||||
|
||||
"""
|
||||
from __future__ import absolute_import, unicode_literals
|
||||
import base64
|
||||
import hashlib
|
||||
import logging
|
||||
import urllib.parse as urlparse
|
||||
|
||||
from oauthlib.common import (
|
||||
Request, generate_nonce, generate_timestamp, to_unicode, urlencode,
|
||||
)
|
||||
|
||||
from . import parameters, signature
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
import sys
|
||||
try:
|
||||
import urlparse
|
||||
except ImportError:
|
||||
import urllib.parse as urlparse
|
||||
|
||||
from oauthlib.common import Request, urlencode, generate_nonce
|
||||
from oauthlib.common import generate_timestamp, to_unicode
|
||||
from . import parameters, signature
|
||||
# Available signature methods
|
||||
#
|
||||
# Note: SIGNATURE_HMAC and SIGNATURE_RSA are kept for backward compatibility
|
||||
# with previous versions of this library, when it the only HMAC-based and
|
||||
# RSA-based signature methods were HMAC-SHA1 and RSA-SHA1. But now that it
|
||||
# supports other hashing algorithms besides SHA1, explicitly identifying which
|
||||
# hashing algorithm is being used is recommended.
|
||||
#
|
||||
# Note: if additional values are defined here, don't forget to update the
|
||||
# imports in "../__init__.py" so they are available outside this module.
|
||||
|
||||
SIGNATURE_HMAC_SHA1 = "HMAC-SHA1"
|
||||
SIGNATURE_HMAC_SHA256 = "HMAC-SHA256"
|
||||
SIGNATURE_HMAC = SIGNATURE_HMAC_SHA1
|
||||
SIGNATURE_RSA = "RSA-SHA1"
|
||||
SIGNATURE_HMAC_SHA512 = "HMAC-SHA512"
|
||||
SIGNATURE_HMAC = SIGNATURE_HMAC_SHA1 # deprecated variable for HMAC-SHA1
|
||||
|
||||
SIGNATURE_RSA_SHA1 = "RSA-SHA1"
|
||||
SIGNATURE_RSA_SHA256 = "RSA-SHA256"
|
||||
SIGNATURE_RSA_SHA512 = "RSA-SHA512"
|
||||
SIGNATURE_RSA = SIGNATURE_RSA_SHA1 # deprecated variable for RSA-SHA1
|
||||
|
||||
SIGNATURE_PLAINTEXT = "PLAINTEXT"
|
||||
SIGNATURE_METHODS = (SIGNATURE_HMAC_SHA1, SIGNATURE_HMAC_SHA256, SIGNATURE_RSA, SIGNATURE_PLAINTEXT)
|
||||
|
||||
SIGNATURE_METHODS = (
|
||||
SIGNATURE_HMAC_SHA1,
|
||||
SIGNATURE_HMAC_SHA256,
|
||||
SIGNATURE_HMAC_SHA512,
|
||||
SIGNATURE_RSA_SHA1,
|
||||
SIGNATURE_RSA_SHA256,
|
||||
SIGNATURE_RSA_SHA512,
|
||||
SIGNATURE_PLAINTEXT
|
||||
)
|
||||
|
||||
SIGNATURE_TYPE_AUTH_HEADER = 'AUTH_HEADER'
|
||||
SIGNATURE_TYPE_QUERY = 'QUERY'
|
||||
|
@ -36,13 +71,16 @@ SIGNATURE_TYPE_BODY = 'BODY'
|
|||
CONTENT_TYPE_FORM_URLENCODED = 'application/x-www-form-urlencoded'
|
||||
|
||||
|
||||
class Client(object):
|
||||
class Client:
|
||||
|
||||
"""A client used to sign OAuth 1.0 RFC 5849 requests."""
|
||||
SIGNATURE_METHODS = {
|
||||
SIGNATURE_HMAC_SHA1: signature.sign_hmac_sha1_with_client,
|
||||
SIGNATURE_HMAC_SHA256: signature.sign_hmac_sha256_with_client,
|
||||
SIGNATURE_RSA: signature.sign_rsa_sha1_with_client,
|
||||
SIGNATURE_HMAC_SHA512: signature.sign_hmac_sha512_with_client,
|
||||
SIGNATURE_RSA_SHA1: signature.sign_rsa_sha1_with_client,
|
||||
SIGNATURE_RSA_SHA256: signature.sign_rsa_sha256_with_client,
|
||||
SIGNATURE_RSA_SHA512: signature.sign_rsa_sha512_with_client,
|
||||
SIGNATURE_PLAINTEXT: signature.sign_plaintext_with_client
|
||||
}
|
||||
|
||||
|
@ -106,8 +144,8 @@ class Client(object):
|
|||
attrs['rsa_key'] = '****' if attrs['rsa_key'] else None
|
||||
attrs[
|
||||
'resource_owner_secret'] = '****' if attrs['resource_owner_secret'] else None
|
||||
attribute_str = ', '.join('%s=%s' % (k, v) for k, v in attrs.items())
|
||||
return '<%s %s>' % (self.__class__.__name__, attribute_str)
|
||||
attribute_str = ', '.join('{}={}'.format(k, v) for k, v in attrs.items())
|
||||
return '<{} {}>'.format(self.__class__.__name__, attribute_str)
|
||||
|
||||
def get_oauth_signature(self, request):
|
||||
"""Get an OAuth signature to be used in signing a request
|
||||
|
@ -130,24 +168,24 @@ class Client(object):
|
|||
uri_query=urlparse.urlparse(uri).query,
|
||||
body=body,
|
||||
headers=headers)
|
||||
log.debug("Collected params: {0}".format(collected_params))
|
||||
log.debug("Collected params: {}".format(collected_params))
|
||||
|
||||
normalized_params = signature.normalize_parameters(collected_params)
|
||||
normalized_uri = signature.base_string_uri(uri, headers.get('Host', None))
|
||||
log.debug("Normalized params: {0}".format(normalized_params))
|
||||
log.debug("Normalized URI: {0}".format(normalized_uri))
|
||||
log.debug("Normalized params: {}".format(normalized_params))
|
||||
log.debug("Normalized URI: {}".format(normalized_uri))
|
||||
|
||||
base_string = signature.signature_base_string(request.http_method,
|
||||
normalized_uri, normalized_params)
|
||||
|
||||
log.debug("Signing: signature base string: {0}".format(base_string))
|
||||
log.debug("Signing: signature base string: {}".format(base_string))
|
||||
|
||||
if self.signature_method not in self.SIGNATURE_METHODS:
|
||||
raise ValueError('Invalid signature method.')
|
||||
|
||||
sig = self.SIGNATURE_METHODS[self.signature_method](base_string, self)
|
||||
|
||||
log.debug("Signature: {0}".format(sig))
|
||||
log.debug("Signature: {}".format(sig))
|
||||
return sig
|
||||
|
||||
def get_oauth_params(self, request):
|
||||
|
@ -278,8 +316,8 @@ class Client(object):
|
|||
# header field set to "application/x-www-form-urlencoded".
|
||||
elif not should_have_params and has_params:
|
||||
raise ValueError(
|
||||
"Body contains parameters but Content-Type header was {0} "
|
||||
"instead of {1}".format(content_type or "not set",
|
||||
"Body contains parameters but Content-Type header was {} "
|
||||
"instead of {}".format(content_type or "not set",
|
||||
CONTENT_TYPE_FORM_URLENCODED))
|
||||
|
||||
# 3.5.2. Form-Encoded Body
|
||||
|
|
|
@ -1,9 +1,8 @@
|
|||
from __future__ import absolute_import
|
||||
|
||||
from .access_token import AccessTokenEndpoint
|
||||
from .authorization import AuthorizationEndpoint
|
||||
from .base import BaseEndpoint
|
||||
from .request_token import RequestTokenEndpoint
|
||||
from .authorization import AuthorizationEndpoint
|
||||
from .access_token import AccessTokenEndpoint
|
||||
from .resource import ResourceEndpoint
|
||||
from .signature_only import SignatureOnlyEndpoint
|
||||
from .pre_configured import WebApplicationServer
|
||||
|
||||
from .pre_configured import WebApplicationServer # isort:skip
|
||||
|
|
|
@ -8,8 +8,6 @@ OAuth 1.0 RFC 5849. It validates the correctness of access token requests,
|
|||
creates and persists tokens as well as create the proper response to be
|
||||
returned to the client.
|
||||
"""
|
||||
from __future__ import absolute_import, unicode_literals
|
||||
|
||||
import logging
|
||||
|
||||
from oauthlib.common import urlencode
|
||||
|
|
|
@ -6,18 +6,13 @@ oauthlib.oauth1.rfc5849.endpoints.authorization
|
|||
This module is an implementation of various logic needed
|
||||
for signing and checking OAuth 1.0 RFC 5849 requests.
|
||||
"""
|
||||
from __future__ import absolute_import, unicode_literals
|
||||
from urllib.parse import urlencode
|
||||
|
||||
from oauthlib.common import Request, add_params_to_uri
|
||||
from oauthlib.common import add_params_to_uri
|
||||
|
||||
from .. import errors
|
||||
from .base import BaseEndpoint
|
||||
|
||||
try:
|
||||
from urllib import urlencode
|
||||
except ImportError:
|
||||
from urllib.parse import urlencode
|
||||
|
||||
|
||||
class AuthorizationEndpoint(BaseEndpoint):
|
||||
|
||||
|
|
|
@ -6,18 +6,19 @@ oauthlib.oauth1.rfc5849.endpoints.base
|
|||
This module is an implementation of various logic needed
|
||||
for signing and checking OAuth 1.0 RFC 5849 requests.
|
||||
"""
|
||||
from __future__ import absolute_import, unicode_literals
|
||||
|
||||
import time
|
||||
|
||||
from oauthlib.common import CaseInsensitiveDict, Request, generate_token
|
||||
|
||||
from .. import (CONTENT_TYPE_FORM_URLENCODED, SIGNATURE_HMAC_SHA1, SIGNATURE_HMAC_SHA256, SIGNATURE_RSA,
|
||||
SIGNATURE_TYPE_AUTH_HEADER, SIGNATURE_TYPE_BODY,
|
||||
SIGNATURE_TYPE_QUERY, errors, signature, utils)
|
||||
from .. import (
|
||||
CONTENT_TYPE_FORM_URLENCODED, SIGNATURE_HMAC_SHA1, SIGNATURE_HMAC_SHA256,
|
||||
SIGNATURE_HMAC_SHA512, SIGNATURE_PLAINTEXT, SIGNATURE_RSA_SHA1,
|
||||
SIGNATURE_RSA_SHA256, SIGNATURE_RSA_SHA512, SIGNATURE_TYPE_AUTH_HEADER,
|
||||
SIGNATURE_TYPE_BODY, SIGNATURE_TYPE_QUERY, errors, signature, utils,
|
||||
)
|
||||
|
||||
|
||||
class BaseEndpoint(object):
|
||||
class BaseEndpoint:
|
||||
|
||||
def __init__(self, request_validator, token_generator=None):
|
||||
self.request_validator = request_validator
|
||||
|
@ -131,7 +132,7 @@ class BaseEndpoint(object):
|
|||
if (not request.signature_method in
|
||||
self.request_validator.allowed_signature_methods):
|
||||
raise errors.InvalidSignatureMethodError(
|
||||
description="Invalid signature, %s not in %r." % (
|
||||
description="Invalid signature, {} not in {!r}.".format(
|
||||
request.signature_method,
|
||||
self.request_validator.allowed_signature_methods))
|
||||
|
||||
|
@ -179,38 +180,65 @@ class BaseEndpoint(object):
|
|||
|
||||
def _check_signature(self, request, is_token_request=False):
|
||||
# ---- RSA Signature verification ----
|
||||
if request.signature_method == SIGNATURE_RSA:
|
||||
if request.signature_method == SIGNATURE_RSA_SHA1 or \
|
||||
request.signature_method == SIGNATURE_RSA_SHA256 or \
|
||||
request.signature_method == SIGNATURE_RSA_SHA512:
|
||||
# RSA-based signature method
|
||||
|
||||
# The server verifies the signature per `[RFC3447] section 8.2.2`_
|
||||
# .. _`[RFC3447] section 8.2.2`: https://tools.ietf.org/html/rfc3447#section-8.2.1
|
||||
|
||||
rsa_key = self.request_validator.get_rsa_key(
|
||||
request.client_key, request)
|
||||
valid_signature = signature.verify_rsa_sha1(request, rsa_key)
|
||||
|
||||
if request.signature_method == SIGNATURE_RSA_SHA1:
|
||||
valid_signature = signature.verify_rsa_sha1(request, rsa_key)
|
||||
elif request.signature_method == SIGNATURE_RSA_SHA256:
|
||||
valid_signature = signature.verify_rsa_sha256(request, rsa_key)
|
||||
elif request.signature_method == SIGNATURE_RSA_SHA512:
|
||||
valid_signature = signature.verify_rsa_sha512(request, rsa_key)
|
||||
else:
|
||||
valid_signature = False
|
||||
|
||||
# ---- HMAC or Plaintext Signature verification ----
|
||||
else:
|
||||
# Non-RSA based signature method
|
||||
|
||||
# Servers receiving an authenticated request MUST validate it by:
|
||||
# Recalculating the request signature independently as described in
|
||||
# `Section 3.4`_ and comparing it to the value received from the
|
||||
# client via the "oauth_signature" parameter.
|
||||
# .. _`Section 3.4`: https://tools.ietf.org/html/rfc5849#section-3.4
|
||||
|
||||
client_secret = self.request_validator.get_client_secret(
|
||||
request.client_key, request)
|
||||
|
||||
resource_owner_secret = None
|
||||
if request.resource_owner_key:
|
||||
if is_token_request:
|
||||
resource_owner_secret = self.request_validator.get_request_token_secret(
|
||||
request.client_key, request.resource_owner_key, request)
|
||||
resource_owner_secret = \
|
||||
self.request_validator.get_request_token_secret(
|
||||
request.client_key, request.resource_owner_key,
|
||||
request)
|
||||
else:
|
||||
resource_owner_secret = self.request_validator.get_access_token_secret(
|
||||
request.client_key, request.resource_owner_key, request)
|
||||
resource_owner_secret = \
|
||||
self.request_validator.get_access_token_secret(
|
||||
request.client_key, request.resource_owner_key,
|
||||
request)
|
||||
|
||||
if request.signature_method == SIGNATURE_HMAC_SHA1:
|
||||
valid_signature = signature.verify_hmac_sha1(request,
|
||||
client_secret, resource_owner_secret)
|
||||
valid_signature = signature.verify_hmac_sha1(
|
||||
request, client_secret, resource_owner_secret)
|
||||
elif request.signature_method == SIGNATURE_HMAC_SHA256:
|
||||
valid_signature = signature.verify_hmac_sha256(request,
|
||||
client_secret, resource_owner_secret)
|
||||
valid_signature = signature.verify_hmac_sha256(
|
||||
request, client_secret, resource_owner_secret)
|
||||
elif request.signature_method == SIGNATURE_HMAC_SHA512:
|
||||
valid_signature = signature.verify_hmac_sha512(
|
||||
request, client_secret, resource_owner_secret)
|
||||
elif request.signature_method == SIGNATURE_PLAINTEXT:
|
||||
valid_signature = signature.verify_plaintext(
|
||||
request, client_secret, resource_owner_secret)
|
||||
else:
|
||||
valid_signature = signature.verify_plaintext(request,
|
||||
client_secret, resource_owner_secret)
|
||||
valid_signature = False
|
||||
|
||||
return valid_signature
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
from __future__ import absolute_import, unicode_literals
|
||||
|
||||
from . import (AccessTokenEndpoint, AuthorizationEndpoint,
|
||||
RequestTokenEndpoint, ResourceEndpoint)
|
||||
from . import (
|
||||
AccessTokenEndpoint, AuthorizationEndpoint, RequestTokenEndpoint,
|
||||
ResourceEndpoint,
|
||||
)
|
||||
|
||||
|
||||
class WebApplicationServer(RequestTokenEndpoint, AuthorizationEndpoint,
|
||||
|
|
|
@ -8,8 +8,6 @@ OAuth 1.0 RFC 5849. It validates the correctness of request token requests,
|
|||
creates and persists tokens as well as create the proper response to be
|
||||
returned to the client.
|
||||
"""
|
||||
from __future__ import absolute_import, unicode_literals
|
||||
|
||||
import logging
|
||||
|
||||
from oauthlib.common import urlencode
|
||||
|
@ -129,7 +127,7 @@ class RequestTokenEndpoint(BaseEndpoint):
|
|||
request.client_key, request)
|
||||
if not self.request_validator.check_realms(request.realms):
|
||||
raise errors.InvalidRequestError(
|
||||
description='Invalid realm %s. Allowed are %r.' % (
|
||||
description='Invalid realm {}. Allowed are {!r}.'.format(
|
||||
request.realms, self.request_validator.realms))
|
||||
|
||||
if not request.redirect_uri:
|
||||
|
@ -154,7 +152,7 @@ class RequestTokenEndpoint(BaseEndpoint):
|
|||
request.client_key = self.request_validator.dummy_client
|
||||
|
||||
# Note that `realm`_ is only used in authorization headers and how
|
||||
# it should be interepreted is not included in the OAuth spec.
|
||||
# it should be interpreted is not included in the OAuth spec.
|
||||
# However they could be seen as a scope or realm to which the
|
||||
# client has access and as such every client should be checked
|
||||
# to ensure it is authorized access to that scope or realm.
|
||||
|
@ -166,7 +164,7 @@ class RequestTokenEndpoint(BaseEndpoint):
|
|||
# workflow where a client requests access to a specific realm.
|
||||
# This first step (obtaining request token) need not require a realm
|
||||
# and can then be identified by checking the require_resource_owner
|
||||
# flag and abscence of realm.
|
||||
# flag and absence of realm.
|
||||
#
|
||||
# Clients obtaining an access token will not supply a realm and it will
|
||||
# not be checked. Instead the previously requested realm should be
|
||||
|
|
|
@ -6,8 +6,6 @@ oauthlib.oauth1.rfc5849.endpoints.resource
|
|||
This module is an implementation of the resource protection provider logic of
|
||||
OAuth 1.0 RFC 5849.
|
||||
"""
|
||||
from __future__ import absolute_import, unicode_literals
|
||||
|
||||
import logging
|
||||
|
||||
from .. import errors
|
||||
|
@ -115,7 +113,7 @@ class ResourceEndpoint(BaseEndpoint):
|
|||
request.resource_owner_key = self.request_validator.dummy_access_token
|
||||
|
||||
# Note that `realm`_ is only used in authorization headers and how
|
||||
# it should be interepreted is not included in the OAuth spec.
|
||||
# it should be interpreted is not included in the OAuth spec.
|
||||
# However they could be seen as a scope or realm to which the
|
||||
# client has access and as such every client should be checked
|
||||
# to ensure it is authorized access to that scope or realm.
|
||||
|
@ -127,7 +125,7 @@ class ResourceEndpoint(BaseEndpoint):
|
|||
# workflow where a client requests access to a specific realm.
|
||||
# This first step (obtaining request token) need not require a realm
|
||||
# and can then be identified by checking the require_resource_owner
|
||||
# flag and abscence of realm.
|
||||
# flag and absence of realm.
|
||||
#
|
||||
# Clients obtaining an access token will not supply a realm and it will
|
||||
# not be checked. Instead the previously requested realm should be
|
||||
|
|
|
@ -6,8 +6,6 @@ oauthlib.oauth1.rfc5849.endpoints.signature_only
|
|||
This module is an implementation of the signing logic of OAuth 1.0 RFC 5849.
|
||||
"""
|
||||
|
||||
from __future__ import absolute_import, unicode_literals
|
||||
|
||||
import logging
|
||||
|
||||
from .. import errors
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
# coding=utf-8
|
||||
"""
|
||||
oauthlib.oauth1.rfc5849.errors
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
@ -6,8 +5,6 @@ oauthlib.oauth1.rfc5849.errors
|
|||
Error used both by OAuth 1 clients and provicers to represent the spec
|
||||
defined error responses for all four core grant types.
|
||||
"""
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from oauthlib.common import add_params_to_uri, urlencode
|
||||
|
||||
|
||||
|
@ -37,10 +34,10 @@ class OAuth1Error(Exception):
|
|||
request: Oauthlib Request object
|
||||
"""
|
||||
self.description = description or self.description
|
||||
message = '(%s) %s' % (self.error, self.description)
|
||||
message = '({}) {}'.format(self.error, self.description)
|
||||
if request:
|
||||
message += ' ' + repr(request)
|
||||
super(OAuth1Error, self).__init__(message)
|
||||
super().__init__(message)
|
||||
|
||||
self.uri = uri
|
||||
self.status_code = status_code
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
oauthlib.parameters
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
|
@ -7,17 +6,12 @@ This module contains methods related to `section 3.5`_ of the OAuth 1.0a spec.
|
|||
|
||||
.. _`section 3.5`: https://tools.ietf.org/html/rfc5849#section-3.5
|
||||
"""
|
||||
from __future__ import absolute_import, unicode_literals
|
||||
from urllib.parse import urlparse, urlunparse
|
||||
|
||||
from oauthlib.common import extract_params, urlencode
|
||||
|
||||
from . import utils
|
||||
|
||||
try:
|
||||
from urlparse import urlparse, urlunparse
|
||||
except ImportError: # noqa
|
||||
from urllib.parse import urlparse, urlunparse
|
||||
|
||||
|
||||
# TODO: do we need filter_params now that oauth_params are handled by Request?
|
||||
# We can easily pass in just oauth protocol params.
|
||||
|
@ -61,7 +55,7 @@ def prepare_headers(oauth_params, headers=None, realm=None):
|
|||
# 2. Each parameter's name is immediately followed by an "=" character
|
||||
# (ASCII code 61), a """ character (ASCII code 34), the parameter
|
||||
# value (MAY be empty), and another """ character (ASCII code 34).
|
||||
part = '{0}="{1}"'.format(escaped_name, escaped_value)
|
||||
part = '{}="{}"'.format(escaped_name, escaped_value)
|
||||
|
||||
authorization_header_parameters_parts.append(part)
|
||||
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
oauthlib.oauth1.rfc5849
|
||||
~~~~~~~~~~~~~~
|
||||
|
@ -6,14 +5,10 @@ oauthlib.oauth1.rfc5849
|
|||
This module is an implementation of various logic needed
|
||||
for signing and checking OAuth 1.0 RFC 5849 requests.
|
||||
"""
|
||||
from __future__ import absolute_import, unicode_literals
|
||||
|
||||
import sys
|
||||
|
||||
from . import SIGNATURE_METHODS, utils
|
||||
|
||||
|
||||
class RequestValidator(object):
|
||||
class RequestValidator:
|
||||
|
||||
"""A validator/datastore interaction base class for OAuth 1 providers.
|
||||
|
||||
|
@ -24,7 +19,7 @@ class RequestValidator(object):
|
|||
Methods used to check the format of input parameters. Common tests include
|
||||
length, character set, membership, range or pattern. These tests are
|
||||
referred to as `whitelisting or blacklisting`_. Whitelisting is better
|
||||
but blacklisting can be usefull to spot malicious activity.
|
||||
but blacklisting can be useful to spot malicious activity.
|
||||
The following have methods a default implementation:
|
||||
|
||||
- check_client_key
|
||||
|
@ -197,7 +192,7 @@ class RequestValidator(object):
|
|||
|
||||
def check_realms(self, realms):
|
||||
"""Check that the realm is one of a set allowed realms."""
|
||||
return all((r in self.realms for r in realms))
|
||||
return all(r in self.realms for r in realms)
|
||||
|
||||
def _subclass_must_implement(self, fn):
|
||||
"""
|
||||
|
@ -448,7 +443,7 @@ class RequestValidator(object):
|
|||
:type request: oauthlib.common.Request
|
||||
:returns: None
|
||||
|
||||
Per `Section 2.3`__ of the spec:
|
||||
Per `Section 2.3`_ of the spec:
|
||||
|
||||
"The server MUST (...) ensure that the temporary
|
||||
credentials have not expired or been used before."
|
||||
|
@ -836,7 +831,7 @@ class RequestValidator(object):
|
|||
"""Associate an authorization verifier with a request token.
|
||||
|
||||
:param token: A request token string.
|
||||
:param verifier A dictionary containing the oauth_verifier and
|
||||
:param verifier: A dictionary containing the oauth_verifier and
|
||||
oauth_token
|
||||
:param request: OAuthlib request.
|
||||
:type request: oauthlib.common.Request
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,4 +1,3 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
oauthlib.utils
|
||||
~~~~~~~~~~~~~~
|
||||
|
@ -6,15 +5,9 @@ oauthlib.utils
|
|||
This module contains utility methods used by various parts of the OAuth
|
||||
spec.
|
||||
"""
|
||||
from __future__ import absolute_import, unicode_literals
|
||||
|
||||
from oauthlib.common import quote, unicode_type, unquote
|
||||
|
||||
try:
|
||||
import urllib2
|
||||
except ImportError:
|
||||
import urllib.request as urllib2
|
||||
import urllib.request as urllib2
|
||||
|
||||
from oauthlib.common import quote, unquote
|
||||
|
||||
UNICODE_ASCII_CHARACTER_SET = ('abcdefghijklmnopqrstuvwxyz'
|
||||
'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
|
||||
|
@ -52,16 +45,16 @@ def escape(u):
|
|||
.. _`section 3.6`: https://tools.ietf.org/html/rfc5849#section-3.6
|
||||
|
||||
"""
|
||||
if not isinstance(u, unicode_type):
|
||||
if not isinstance(u, str):
|
||||
raise ValueError('Only unicode objects are escapable. ' +
|
||||
'Got %r of type %s.' % (u, type(u)))
|
||||
'Got {!r} of type {}.'.format(u, type(u)))
|
||||
# Letters, digits, and the characters '_.-' are already treated as safe
|
||||
# by urllib.quote(). We need to add '~' to fully support rfc5849.
|
||||
return quote(u, safe=b'~')
|
||||
|
||||
|
||||
def unescape(u):
|
||||
if not isinstance(u, unicode_type):
|
||||
if not isinstance(u, str):
|
||||
raise ValueError('Only unicode objects are unescapable.')
|
||||
return unquote(u)
|
||||
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
oauthlib.oauth2
|
||||
~~~~~~~~~~~~~~
|
||||
|
@ -6,31 +5,32 @@ oauthlib.oauth2
|
|||
This module is a wrapper for the most recent implementation of OAuth 2.0 Client
|
||||
and Server classes.
|
||||
"""
|
||||
from __future__ import absolute_import, unicode_literals
|
||||
|
||||
from .rfc6749.clients import Client
|
||||
from .rfc6749.clients import WebApplicationClient
|
||||
from .rfc6749.clients import MobileApplicationClient
|
||||
from .rfc6749.clients import LegacyApplicationClient
|
||||
from .rfc6749.clients import BackendApplicationClient
|
||||
from .rfc6749.clients import ServiceApplicationClient
|
||||
from .rfc6749.endpoints import AuthorizationEndpoint
|
||||
from .rfc6749.endpoints import IntrospectEndpoint
|
||||
from .rfc6749.endpoints import MetadataEndpoint
|
||||
from .rfc6749.endpoints import TokenEndpoint
|
||||
from .rfc6749.endpoints import ResourceEndpoint
|
||||
from .rfc6749.endpoints import RevocationEndpoint
|
||||
from .rfc6749.endpoints import Server
|
||||
from .rfc6749.endpoints import WebApplicationServer
|
||||
from .rfc6749.endpoints import MobileApplicationServer
|
||||
from .rfc6749.endpoints import LegacyApplicationServer
|
||||
from .rfc6749.endpoints import BackendApplicationServer
|
||||
from .rfc6749.errors import AccessDeniedError, OAuth2Error, FatalClientError, InsecureTransportError, InvalidClientError, InvalidClientIdError, InvalidGrantError, InvalidRedirectURIError, InvalidRequestError, InvalidRequestFatalError, InvalidScopeError, MismatchingRedirectURIError, MismatchingStateError, MissingClientIdError, MissingCodeError, MissingRedirectURIError, MissingResponseTypeError, MissingTokenError, MissingTokenTypeError, ServerError, TemporarilyUnavailableError, TokenExpiredError, UnauthorizedClientError, UnsupportedGrantTypeError, UnsupportedResponseTypeError, UnsupportedTokenTypeError
|
||||
from .rfc6749.grant_types import AuthorizationCodeGrant
|
||||
from .rfc6749.grant_types import ImplicitGrant
|
||||
from .rfc6749.grant_types import ResourceOwnerPasswordCredentialsGrant
|
||||
from .rfc6749.grant_types import ClientCredentialsGrant
|
||||
from .rfc6749.grant_types import RefreshTokenGrant
|
||||
from .rfc6749.clients import (
|
||||
BackendApplicationClient, Client, LegacyApplicationClient,
|
||||
MobileApplicationClient, ServiceApplicationClient, WebApplicationClient,
|
||||
)
|
||||
from .rfc6749.endpoints import (
|
||||
AuthorizationEndpoint, BackendApplicationServer, IntrospectEndpoint,
|
||||
LegacyApplicationServer, MetadataEndpoint, MobileApplicationServer,
|
||||
ResourceEndpoint, RevocationEndpoint, Server, TokenEndpoint,
|
||||
WebApplicationServer,
|
||||
)
|
||||
from .rfc6749.errors import (
|
||||
AccessDeniedError, FatalClientError, InsecureTransportError,
|
||||
InvalidClientError, InvalidClientIdError, InvalidGrantError,
|
||||
InvalidRedirectURIError, InvalidRequestError, InvalidRequestFatalError,
|
||||
InvalidScopeError, MismatchingRedirectURIError, MismatchingStateError,
|
||||
MissingClientIdError, MissingCodeError, MissingRedirectURIError,
|
||||
MissingResponseTypeError, MissingTokenError, MissingTokenTypeError,
|
||||
OAuth2Error, ServerError, TemporarilyUnavailableError, TokenExpiredError,
|
||||
UnauthorizedClientError, UnsupportedGrantTypeError,
|
||||
UnsupportedResponseTypeError, UnsupportedTokenTypeError,
|
||||
)
|
||||
from .rfc6749.grant_types import (
|
||||
AuthorizationCodeGrant, ClientCredentialsGrant, ImplicitGrant,
|
||||
RefreshTokenGrant, ResourceOwnerPasswordCredentialsGrant,
|
||||
)
|
||||
from .rfc6749.request_validator import RequestValidator
|
||||
from .rfc6749.tokens import BearerToken, OAuth2Token
|
||||
from .rfc6749.utils import is_secure_transport
|
||||
from .rfc8628.clients import DeviceClient
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
oauthlib.oauth2.rfc6749
|
||||
~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
@ -6,15 +5,12 @@ oauthlib.oauth2.rfc6749
|
|||
This module is an implementation of various logic needed
|
||||
for consuming and providing OAuth 2.0 RFC6749.
|
||||
"""
|
||||
from __future__ import absolute_import, unicode_literals
|
||||
|
||||
import functools
|
||||
import logging
|
||||
|
||||
from .endpoints.base import BaseEndpoint
|
||||
from .endpoints.base import catch_errors_and_unavailability
|
||||
from .errors import TemporarilyUnavailableError, ServerError
|
||||
from .errors import FatalClientError, OAuth2Error
|
||||
|
||||
from .endpoints.base import BaseEndpoint, catch_errors_and_unavailability
|
||||
from .errors import (
|
||||
FatalClientError, OAuth2Error, ServerError, TemporarilyUnavailableError,
|
||||
)
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
|
|
@ -6,11 +6,9 @@ oauthlib.oauth2.rfc6749
|
|||
This module is an implementation of various logic needed
|
||||
for consuming OAuth 2.0 RFC6749.
|
||||
"""
|
||||
from __future__ import absolute_import, unicode_literals
|
||||
|
||||
from .base import Client, AUTH_HEADER, URI_QUERY, BODY
|
||||
from .web_application import WebApplicationClient
|
||||
from .mobile_application import MobileApplicationClient
|
||||
from .legacy_application import LegacyApplicationClient
|
||||
from .backend_application import BackendApplicationClient
|
||||
from .base import AUTH_HEADER, BODY, URI_QUERY, Client
|
||||
from .legacy_application import LegacyApplicationClient
|
||||
from .mobile_application import MobileApplicationClient
|
||||
from .service_application import ServiceApplicationClient
|
||||
from .web_application import WebApplicationClient
|
||||
|
|
|
@ -6,9 +6,7 @@ oauthlib.oauth2.rfc6749
|
|||
This module is an implementation of various logic needed
|
||||
for consuming and providing OAuth 2.0 RFC6749.
|
||||
"""
|
||||
from __future__ import absolute_import, unicode_literals
|
||||
|
||||
from ..parameters import parse_token_response, prepare_token_request
|
||||
from ..parameters import prepare_token_request
|
||||
from .base import Client
|
||||
|
||||
|
||||
|
@ -41,7 +39,7 @@ class BackendApplicationClient(Client):
|
|||
format per `Appendix B`_ in the HTTP request entity-body:
|
||||
|
||||
:param body: Existing request body (URL encoded string) to embed parameters
|
||||
into. This may contain extra paramters. Default ''.
|
||||
into. This may contain extra parameters. Default ''.
|
||||
:param scope: The scope of the access request as described by
|
||||
`Section 3.3`_.
|
||||
|
||||
|
|
|
@ -6,18 +6,22 @@ oauthlib.oauth2.rfc6749
|
|||
This module is an implementation of various logic needed
|
||||
for consuming OAuth 2.0 RFC6749.
|
||||
"""
|
||||
from __future__ import absolute_import, unicode_literals
|
||||
|
||||
import base64
|
||||
import hashlib
|
||||
import re
|
||||
import secrets
|
||||
import time
|
||||
import warnings
|
||||
|
||||
from oauthlib.common import generate_token
|
||||
from oauthlib.oauth2.rfc6749 import tokens
|
||||
from oauthlib.oauth2.rfc6749.errors import (InsecureTransportError,
|
||||
TokenExpiredError)
|
||||
from oauthlib.oauth2.rfc6749.parameters import (parse_token_response,
|
||||
prepare_token_request,
|
||||
prepare_token_revocation_request)
|
||||
from oauthlib.oauth2.rfc6749.errors import (
|
||||
InsecureTransportError, TokenExpiredError,
|
||||
)
|
||||
from oauthlib.oauth2.rfc6749.parameters import (
|
||||
parse_token_response, prepare_token_request,
|
||||
prepare_token_revocation_request,
|
||||
)
|
||||
from oauthlib.oauth2.rfc6749.utils import is_secure_transport
|
||||
|
||||
AUTH_HEADER = 'auth_header'
|
||||
|
@ -29,7 +33,7 @@ FORM_ENC_HEADERS = {
|
|||
}
|
||||
|
||||
|
||||
class Client(object):
|
||||
class Client:
|
||||
"""Base OAuth2 client responsible for access token management.
|
||||
|
||||
This class also acts as a generic interface providing methods common to all
|
||||
|
@ -61,6 +65,9 @@ class Client(object):
|
|||
state=None,
|
||||
redirect_url=None,
|
||||
state_generator=generate_token,
|
||||
code_verifier=None,
|
||||
code_challenge=None,
|
||||
code_challenge_method=None,
|
||||
**kwargs):
|
||||
"""Initialize a client with commonly used attributes.
|
||||
|
||||
|
@ -99,6 +106,15 @@ class Client(object):
|
|||
|
||||
:param state_generator: A no argument state generation callable. Defaults
|
||||
to :py:meth:`oauthlib.common.generate_token`.
|
||||
|
||||
:param code_verifier: PKCE parameter. A cryptographically random string that is used to correlate the
|
||||
authorization request to the token request.
|
||||
|
||||
:param code_challenge: PKCE parameter. A challenge derived from the code verifier that is sent in the
|
||||
authorization request, to be verified against later.
|
||||
|
||||
:param code_challenge_method: PKCE parameter. A method that was used to derive code challenge.
|
||||
Defaults to "plain" if not present in the request.
|
||||
"""
|
||||
|
||||
self.client_id = client_id
|
||||
|
@ -113,6 +129,9 @@ class Client(object):
|
|||
self.state_generator = state_generator
|
||||
self.state = state
|
||||
self.redirect_url = redirect_url
|
||||
self.code_verifier = code_verifier
|
||||
self.code_challenge = code_challenge
|
||||
self.code_challenge_method = code_challenge_method
|
||||
self.code = None
|
||||
self.expires_in = None
|
||||
self._expires_at = None
|
||||
|
@ -186,8 +205,8 @@ class Client(object):
|
|||
|
||||
token_placement = token_placement or self.default_token_placement
|
||||
|
||||
case_insensitive_token_types = dict(
|
||||
(k.lower(), v) for k, v in self.token_types.items())
|
||||
case_insensitive_token_types = {
|
||||
k.lower(): v for k, v in self.token_types.items()}
|
||||
if not self.token_type.lower() in case_insensitive_token_types:
|
||||
raise ValueError("Unsupported token type: %s" % self.token_type)
|
||||
|
||||
|
@ -209,23 +228,21 @@ class Client(object):
|
|||
required parameters to the authorization URL.
|
||||
|
||||
:param authorization_url: Provider authorization endpoint URL.
|
||||
|
||||
:param state: CSRF protection string. Will be automatically created if
|
||||
not provided. The generated state is available via the ``state``
|
||||
attribute. Clients should verify that the state is unchanged and
|
||||
present in the authorization response. This verification is done
|
||||
automatically if using the ``authorization_response`` parameter
|
||||
with ``prepare_token_request``.
|
||||
|
||||
not provided. The generated state is available via the ``state``
|
||||
attribute. Clients should verify that the state is unchanged and
|
||||
present in the authorization response. This verification is done
|
||||
automatically if using the ``authorization_response`` parameter
|
||||
with ``prepare_token_request``.
|
||||
:param redirect_url: Redirect URL to which the user will be returned
|
||||
after authorization. Must be provided unless previously setup with
|
||||
the provider. If provided then it must also be provided in the
|
||||
token request.
|
||||
|
||||
:param scope:
|
||||
|
||||
after authorization. Must be provided unless previously setup with
|
||||
the provider. If provided then it must also be provided in the
|
||||
token request.
|
||||
:param scope: List of scopes to request. Must be equal to
|
||||
or a subset of the scopes granted when obtaining the refresh
|
||||
token. If none is provided, the ones provided in the constructor are
|
||||
used.
|
||||
:param kwargs: Additional parameters to included in the request.
|
||||
|
||||
:returns: The prepared request tuple with (url, headers, body).
|
||||
"""
|
||||
if not is_secure_transport(authorization_url):
|
||||
|
@ -233,10 +250,11 @@ class Client(object):
|
|||
|
||||
self.state = state or self.state_generator()
|
||||
self.redirect_url = redirect_url or self.redirect_url
|
||||
self.scope = scope or self.scope
|
||||
# do not assign scope to self automatically anymore
|
||||
scope = self.scope if scope is None else scope
|
||||
auth_url = self.prepare_request_uri(
|
||||
authorization_url, redirect_uri=self.redirect_url,
|
||||
scope=self.scope, state=self.state, **kwargs)
|
||||
scope=scope, state=self.state, **kwargs)
|
||||
return auth_url, FORM_ENC_HEADERS, ''
|
||||
|
||||
def prepare_token_request(self, token_url, authorization_response=None,
|
||||
|
@ -248,22 +266,16 @@ class Client(object):
|
|||
credentials.
|
||||
|
||||
:param token_url: Provider token creation endpoint URL.
|
||||
|
||||
:param authorization_response: The full redirection URL string, i.e.
|
||||
the location to which the user was redirected after successfull
|
||||
authorization. Used to mine credentials needed to obtain a token
|
||||
in this step, such as authorization code.
|
||||
|
||||
the location to which the user was redirected after successful
|
||||
authorization. Used to mine credentials needed to obtain a token
|
||||
in this step, such as authorization code.
|
||||
:param redirect_url: The redirect_url supplied with the authorization
|
||||
request (if there was one).
|
||||
|
||||
request (if there was one).
|
||||
:param state:
|
||||
|
||||
:param body: Existing request body (URL encoded string) to embed parameters
|
||||
into. This may contain extra paramters. Default ''.
|
||||
|
||||
into. This may contain extra parameters. Default ''.
|
||||
:param kwargs: Additional parameters to included in the request.
|
||||
|
||||
:returns: The prepared request tuple with (url, headers, body).
|
||||
"""
|
||||
if not is_secure_transport(token_url):
|
||||
|
@ -289,26 +301,23 @@ class Client(object):
|
|||
obtain a new access token, and possibly a new refresh token.
|
||||
|
||||
:param token_url: Provider token refresh endpoint URL.
|
||||
|
||||
:param refresh_token: Refresh token string.
|
||||
|
||||
:param body: Existing request body (URL encoded string) to embed parameters
|
||||
into. This may contain extra paramters. Default ''.
|
||||
|
||||
into. This may contain extra parameters. Default ''.
|
||||
:param scope: List of scopes to request. Must be equal to
|
||||
or a subset of the scopes granted when obtaining the refresh
|
||||
token.
|
||||
|
||||
or a subset of the scopes granted when obtaining the refresh
|
||||
token. If none is provided, the ones provided in the constructor are
|
||||
used.
|
||||
:param kwargs: Additional parameters to included in the request.
|
||||
|
||||
:returns: The prepared request tuple with (url, headers, body).
|
||||
"""
|
||||
if not is_secure_transport(token_url):
|
||||
raise InsecureTransportError()
|
||||
|
||||
self.scope = scope or self.scope
|
||||
# do not assign scope to self automatically anymore
|
||||
scope = self.scope if scope is None else scope
|
||||
body = self.prepare_refresh_body(body=body,
|
||||
refresh_token=refresh_token, scope=self.scope, **kwargs)
|
||||
refresh_token=refresh_token, scope=scope, **kwargs)
|
||||
return token_url, FORM_ENC_HEADERS, body
|
||||
|
||||
def prepare_token_revocation_request(self, revocation_url, token,
|
||||
|
@ -316,20 +325,14 @@ class Client(object):
|
|||
"""Prepare a token revocation request.
|
||||
|
||||
:param revocation_url: Provider token revocation endpoint URL.
|
||||
|
||||
:param token: The access or refresh token to be revoked (string).
|
||||
|
||||
:param token_type_hint: ``"access_token"`` (default) or
|
||||
``"refresh_token"``. This is optional and if you wish to not pass it you
|
||||
must provide ``token_type_hint=None``.
|
||||
|
||||
``"refresh_token"``. This is optional and if you wish to not pass it you
|
||||
must provide ``token_type_hint=None``.
|
||||
:param body:
|
||||
|
||||
:param callback: A jsonp callback such as ``package.callback`` to be invoked
|
||||
upon receiving the response. Not that it should not include a () suffix.
|
||||
|
||||
upon receiving the response. Not that it should not include a () suffix.
|
||||
:param kwargs: Additional parameters to included in the request.
|
||||
|
||||
:returns: The prepared request tuple with (url, headers, body).
|
||||
|
||||
Note that JSONP request may use GET requests as the parameters will
|
||||
|
@ -337,7 +340,7 @@ class Client(object):
|
|||
|
||||
An example of a revocation request
|
||||
|
||||
.. code-block: http
|
||||
.. code-block:: http
|
||||
|
||||
POST /revoke HTTP/1.1
|
||||
Host: server.example.com
|
||||
|
@ -348,7 +351,7 @@ class Client(object):
|
|||
|
||||
An example of a jsonp revocation request
|
||||
|
||||
.. code-block: http
|
||||
.. code-block:: http
|
||||
|
||||
GET /revoke?token=agabcdefddddafdd&callback=package.myCallback HTTP/1.1
|
||||
Host: server.example.com
|
||||
|
@ -357,9 +360,9 @@ class Client(object):
|
|||
|
||||
and an error response
|
||||
|
||||
.. code-block: http
|
||||
.. code-block:: javascript
|
||||
|
||||
package.myCallback({"error":"unsupported_token_type"});
|
||||
package.myCallback({"error":"unsupported_token_type"});
|
||||
|
||||
Note that these requests usually require client credentials, client_id in
|
||||
the case for public clients and provider specific authentication
|
||||
|
@ -382,9 +385,11 @@ class Client(object):
|
|||
returns an error response as described in `Section 5.2`_.
|
||||
|
||||
:param body: The response body from the token request.
|
||||
:param scope: Scopes originally requested.
|
||||
:param scope: Scopes originally requested. If none is provided, the ones
|
||||
provided in the constructor are used.
|
||||
:return: Dictionary of token parameters.
|
||||
:raises: Warning if scope has changed. OAuth2Error if response is invalid.
|
||||
:raises: Warning if scope has changed. :py:class:`oauthlib.oauth2.errors.OAuth2Error`
|
||||
if response is invalid.
|
||||
|
||||
These response are json encoded and could easily be parsed without
|
||||
the assistance of OAuthLib. However, there are a few subtle issues
|
||||
|
@ -410,7 +415,7 @@ class Client(object):
|
|||
If omitted, the authorization server SHOULD provide the
|
||||
expiration time via other means or document the default value.
|
||||
|
||||
**scope**
|
||||
**scope**
|
||||
Providers may supply this in all responses but are required to only
|
||||
if it has changed since the authorization request.
|
||||
|
||||
|
@ -418,6 +423,7 @@ class Client(object):
|
|||
.. _`Section 5.2`: https://tools.ietf.org/html/rfc6749#section-5.2
|
||||
.. _`Section 7.1`: https://tools.ietf.org/html/rfc6749#section-7.1
|
||||
"""
|
||||
scope = self.scope if scope is None else scope
|
||||
self.token = parse_token_response(body, scope=scope)
|
||||
self.populate_token_attributes(self.token)
|
||||
return self.token
|
||||
|
@ -427,21 +433,19 @@ class Client(object):
|
|||
|
||||
If the authorization server issued a refresh token to the client, the
|
||||
client makes a refresh request to the token endpoint by adding the
|
||||
following parameters using the "application/x-www-form-urlencoded"
|
||||
following parameters using the `application/x-www-form-urlencoded`
|
||||
format in the HTTP request entity-body:
|
||||
|
||||
grant_type
|
||||
REQUIRED. Value MUST be set to "refresh_token".
|
||||
refresh_token
|
||||
REQUIRED. The refresh token issued to the client.
|
||||
scope
|
||||
OPTIONAL. The scope of the access request as described by
|
||||
Section 3.3. The requested scope MUST NOT include any scope
|
||||
not originally granted by the resource owner, and if omitted is
|
||||
treated as equal to the scope originally granted by the
|
||||
resource owner.
|
||||
:param refresh_token: REQUIRED. The refresh token issued to the client.
|
||||
:param scope: OPTIONAL. The scope of the access request as described by
|
||||
Section 3.3. The requested scope MUST NOT include any scope
|
||||
not originally granted by the resource owner, and if omitted is
|
||||
treated as equal to the scope originally granted by the
|
||||
resource owner. Note that if none is provided, the ones provided
|
||||
in the constructor are used if any.
|
||||
"""
|
||||
refresh_token = refresh_token or self.refresh_token
|
||||
scope = self.scope if scope is None else scope
|
||||
return prepare_token_request(self.refresh_token_key, body=body, scope=scope,
|
||||
refresh_token=refresh_token, **kwargs)
|
||||
|
||||
|
@ -461,6 +465,91 @@ class Client(object):
|
|||
raise ValueError("Invalid token placement.")
|
||||
return uri, headers, body
|
||||
|
||||
def create_code_verifier(self, length):
|
||||
"""Create PKCE **code_verifier** used in computing **code_challenge**.
|
||||
See `RFC7636 Section 4.1`_
|
||||
|
||||
:param length: REQUIRED. The length of the code_verifier.
|
||||
|
||||
The client first creates a code verifier, "code_verifier", for each
|
||||
OAuth 2.0 [RFC6749] Authorization Request, in the following manner:
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
code_verifier = high-entropy cryptographic random STRING using the
|
||||
unreserved characters [A-Z] / [a-z] / [0-9] / "-" / "." / "_" / "~"
|
||||
from Section 2.3 of [RFC3986], with a minimum length of 43 characters
|
||||
and a maximum length of 128 characters.
|
||||
|
||||
.. _`RFC7636 Section 4.1`: https://tools.ietf.org/html/rfc7636#section-4.1
|
||||
"""
|
||||
code_verifier = None
|
||||
|
||||
if not length >= 43:
|
||||
raise ValueError("Length must be greater than or equal to 43")
|
||||
|
||||
if not length <= 128:
|
||||
raise ValueError("Length must be less than or equal to 128")
|
||||
|
||||
allowed_characters = re.compile('^[A-Zaa-z0-9-._~]')
|
||||
code_verifier = secrets.token_urlsafe(length)
|
||||
|
||||
if not re.search(allowed_characters, code_verifier):
|
||||
raise ValueError("code_verifier contains invalid characters")
|
||||
|
||||
self.code_verifier = code_verifier
|
||||
|
||||
return code_verifier
|
||||
|
||||
def create_code_challenge(self, code_verifier, code_challenge_method=None):
|
||||
"""Create PKCE **code_challenge** derived from the **code_verifier**.
|
||||
See `RFC7636 Section 4.2`_
|
||||
|
||||
:param code_verifier: REQUIRED. The **code_verifier** generated from `create_code_verifier()`.
|
||||
:param code_challenge_method: OPTIONAL. The method used to derive the **code_challenge**. Acceptable values include `S256`. DEFAULT is `plain`.
|
||||
|
||||
The client then creates a code challenge derived from the code
|
||||
verifier by using one of the following transformations on the code
|
||||
verifier::
|
||||
|
||||
plain
|
||||
code_challenge = code_verifier
|
||||
S256
|
||||
code_challenge = BASE64URL-ENCODE(SHA256(ASCII(code_verifier)))
|
||||
|
||||
If the client is capable of using `S256`, it MUST use `S256`, as
|
||||
`S256` is Mandatory To Implement (MTI) on the server. Clients are
|
||||
permitted to use `plain` only if they cannot support `S256` for some
|
||||
technical reason and know via out-of-band configuration that the
|
||||
server supports `plain`.
|
||||
|
||||
The plain transformation is for compatibility with existing
|
||||
deployments and for constrained environments that can't use the S256 transformation.
|
||||
|
||||
.. _`RFC7636 Section 4.2`: https://tools.ietf.org/html/rfc7636#section-4.2
|
||||
"""
|
||||
code_challenge = None
|
||||
|
||||
if code_verifier == None:
|
||||
raise ValueError("Invalid code_verifier")
|
||||
|
||||
if code_challenge_method == None:
|
||||
code_challenge_method = "plain"
|
||||
self.code_challenge_method = code_challenge_method
|
||||
code_challenge = code_verifier
|
||||
self.code_challenge = code_challenge
|
||||
|
||||
if code_challenge_method == "S256":
|
||||
h = hashlib.sha256()
|
||||
h.update(code_verifier.encode(encoding='ascii'))
|
||||
sha256_val = h.digest()
|
||||
code_challenge = bytes.decode(base64.urlsafe_b64encode(sha256_val))
|
||||
# replace '+' with '-', '/' with '_', and remove trailing '='
|
||||
code_challenge = code_challenge.replace("+", "-").replace("/", "_").replace("=", "")
|
||||
self.code_challenge = code_challenge
|
||||
|
||||
return code_challenge
|
||||
|
||||
def _add_mac_token(self, uri, http_method='GET', body=None,
|
||||
headers=None, token_placement=AUTH_HEADER, ext=None, **kwargs):
|
||||
"""Add a MAC token to the request authorization header.
|
||||
|
@ -503,7 +592,10 @@ class Client(object):
|
|||
self._expires_at = time.time() + int(self.expires_in)
|
||||
|
||||
if 'expires_at' in response:
|
||||
self._expires_at = int(response.get('expires_at'))
|
||||
try:
|
||||
self._expires_at = int(response.get('expires_at'))
|
||||
except:
|
||||
self._expires_at = None
|
||||
|
||||
if 'mac_key' in response:
|
||||
self.mac_key = response.get('mac_key')
|
||||
|
|
|
@ -6,9 +6,7 @@ oauthlib.oauth2.rfc6749
|
|||
This module is an implementation of various logic needed
|
||||
for consuming and providing OAuth 2.0 RFC6749.
|
||||
"""
|
||||
from __future__ import absolute_import, unicode_literals
|
||||
|
||||
from ..parameters import parse_token_response, prepare_token_request
|
||||
from ..parameters import prepare_token_request
|
||||
from .base import Client
|
||||
|
||||
|
||||
|
@ -38,7 +36,7 @@ class LegacyApplicationClient(Client):
|
|||
grant_type = 'password'
|
||||
|
||||
def __init__(self, client_id, **kwargs):
|
||||
super(LegacyApplicationClient, self).__init__(client_id, **kwargs)
|
||||
super().__init__(client_id, **kwargs)
|
||||
|
||||
def prepare_request_body(self, username, password, body='', scope=None,
|
||||
include_client_id=False, **kwargs):
|
||||
|
@ -51,7 +49,7 @@ class LegacyApplicationClient(Client):
|
|||
:param username: The resource owner username.
|
||||
:param password: The resource owner password.
|
||||
:param body: Existing request body (URL encoded string) to embed parameters
|
||||
into. This may contain extra paramters. Default ''.
|
||||
into. This may contain extra parameters. Default ''.
|
||||
:param scope: The scope of the access request as described by
|
||||
`Section 3.3`_.
|
||||
:param include_client_id: `True` to send the `client_id` in the
|
||||
|
@ -81,5 +79,6 @@ class LegacyApplicationClient(Client):
|
|||
"""
|
||||
kwargs['client_id'] = self.client_id
|
||||
kwargs['include_client_id'] = include_client_id
|
||||
scope = self.scope if scope is None else scope
|
||||
return prepare_token_request(self.grant_type, body=body, username=username,
|
||||
password=password, scope=scope, **kwargs)
|
||||
|
|
|
@ -6,8 +6,6 @@ oauthlib.oauth2.rfc6749
|
|||
This module is an implementation of various logic needed
|
||||
for consuming and providing OAuth 2.0 RFC6749.
|
||||
"""
|
||||
from __future__ import absolute_import, unicode_literals
|
||||
|
||||
from ..parameters import parse_implicit_response, prepare_grant_uri
|
||||
from .base import Client
|
||||
|
||||
|
@ -57,7 +55,7 @@ class MobileApplicationClient(Client):
|
|||
using the "application/x-www-form-urlencoded" format, per `Appendix B`_:
|
||||
|
||||
:param redirect_uri: OPTIONAL. The redirect URI must be an absolute URI
|
||||
and it should have been registerd with the OAuth
|
||||
and it should have been registered with the OAuth
|
||||
provider prior to use. As described in `Section 3.1.2`_.
|
||||
|
||||
:param scope: OPTIONAL. The scope of the access request as described by
|
||||
|
@ -93,6 +91,7 @@ class MobileApplicationClient(Client):
|
|||
.. _`Section 3.3`: https://tools.ietf.org/html/rfc6749#section-3.3
|
||||
.. _`Section 10.12`: https://tools.ietf.org/html/rfc6749#section-10.12
|
||||
"""
|
||||
scope = self.scope if scope is None else scope
|
||||
return prepare_grant_uri(uri, self.client_id, self.response_type,
|
||||
redirect_uri=redirect_uri, state=state, scope=scope, **kwargs)
|
||||
|
||||
|
@ -169,6 +168,7 @@ class MobileApplicationClient(Client):
|
|||
.. _`Section 7.1`: https://tools.ietf.org/html/rfc6749#section-7.1
|
||||
.. _`Section 3.3`: https://tools.ietf.org/html/rfc6749#section-3.3
|
||||
"""
|
||||
scope = self.scope if scope is None else scope
|
||||
self.token = parse_implicit_response(uri, state=state, scope=scope)
|
||||
self.populate_token_attributes(self.token)
|
||||
return self.token
|
||||
|
|
|
@ -6,13 +6,11 @@ oauthlib.oauth2.rfc6749
|
|||
This module is an implementation of various logic needed
|
||||
for consuming and providing OAuth 2.0 RFC6749.
|
||||
"""
|
||||
from __future__ import absolute_import, unicode_literals
|
||||
|
||||
import time
|
||||
|
||||
from oauthlib.common import to_unicode
|
||||
|
||||
from ..parameters import parse_token_response, prepare_token_request
|
||||
from ..parameters import prepare_token_request
|
||||
from .base import Client
|
||||
|
||||
|
||||
|
@ -33,7 +31,7 @@ class ServiceApplicationClient(Client):
|
|||
|
||||
def __init__(self, client_id, private_key=None, subject=None, issuer=None,
|
||||
audience=None, **kwargs):
|
||||
"""Initalize a JWT client with defaults for implicit use later.
|
||||
"""Initialize a JWT client with defaults for implicit use later.
|
||||
|
||||
:param client_id: Client identifier given by the OAuth provider upon
|
||||
registration.
|
||||
|
@ -57,7 +55,7 @@ class ServiceApplicationClient(Client):
|
|||
state and token. See ``Client.__init__.__doc__`` for
|
||||
details.
|
||||
"""
|
||||
super(ServiceApplicationClient, self).__init__(client_id, **kwargs)
|
||||
super().__init__(client_id, **kwargs)
|
||||
self.private_key = private_key
|
||||
self.subject = subject
|
||||
self.issuer = issuer
|
||||
|
@ -101,7 +99,7 @@ class ServiceApplicationClient(Client):
|
|||
:param extra_claims: A dict of additional claims to include in the JWT.
|
||||
|
||||
:param body: Existing request body (URL encoded string) to embed parameters
|
||||
into. This may contain extra paramters. Default ''.
|
||||
into. This may contain extra parameters. Default ''.
|
||||
|
||||
:param scope: The scope of the access request.
|
||||
|
||||
|
@ -183,6 +181,7 @@ class ServiceApplicationClient(Client):
|
|||
|
||||
kwargs['client_id'] = self.client_id
|
||||
kwargs['include_client_id'] = include_client_id
|
||||
scope = self.scope if scope is None else scope
|
||||
return prepare_token_request(self.grant_type,
|
||||
body=body,
|
||||
assertion=assertion,
|
||||
|
|
|
@ -6,13 +6,12 @@ oauthlib.oauth2.rfc6749
|
|||
This module is an implementation of various logic needed
|
||||
for consuming and providing OAuth 2.0 RFC6749.
|
||||
"""
|
||||
from __future__ import absolute_import, unicode_literals
|
||||
|
||||
import warnings
|
||||
|
||||
from ..parameters import (parse_authorization_code_response,
|
||||
parse_token_response, prepare_grant_uri,
|
||||
prepare_token_request)
|
||||
from ..parameters import (
|
||||
parse_authorization_code_response, prepare_grant_uri,
|
||||
prepare_token_request,
|
||||
)
|
||||
from .base import Client
|
||||
|
||||
|
||||
|
@ -38,11 +37,11 @@ class WebApplicationClient(Client):
|
|||
grant_type = 'authorization_code'
|
||||
|
||||
def __init__(self, client_id, code=None, **kwargs):
|
||||
super(WebApplicationClient, self).__init__(client_id, **kwargs)
|
||||
super().__init__(client_id, **kwargs)
|
||||
self.code = code
|
||||
|
||||
def prepare_request_uri(self, uri, redirect_uri=None, scope=None,
|
||||
state=None, **kwargs):
|
||||
state=None, code_challenge=None, code_challenge_method='plain', **kwargs):
|
||||
"""Prepare the authorization code request URI
|
||||
|
||||
The client constructs the request URI by adding the following
|
||||
|
@ -50,7 +49,7 @@ class WebApplicationClient(Client):
|
|||
using the "application/x-www-form-urlencoded" format, per `Appendix B`_:
|
||||
|
||||
:param redirect_uri: OPTIONAL. The redirect URI must be an absolute URI
|
||||
and it should have been registerd with the OAuth
|
||||
and it should have been registered with the OAuth
|
||||
provider prior to use. As described in `Section 3.1.2`_.
|
||||
|
||||
:param scope: OPTIONAL. The scope of the access request as described by
|
||||
|
@ -63,6 +62,13 @@ class WebApplicationClient(Client):
|
|||
to the client. The parameter SHOULD be used for preventing
|
||||
cross-site request forgery as described in `Section 10.12`_.
|
||||
|
||||
:param code_challenge: OPTIONAL. PKCE parameter. REQUIRED if PKCE is enforced.
|
||||
A challenge derived from the code_verifier that is sent in the
|
||||
authorization request, to be verified against later.
|
||||
|
||||
:param code_challenge_method: OPTIONAL. PKCE parameter. A method that was used to derive code challenge.
|
||||
Defaults to "plain" if not present in the request.
|
||||
|
||||
:param kwargs: Extra arguments to include in the request URI.
|
||||
|
||||
In addition to supplied parameters, OAuthLib will append the ``client_id``
|
||||
|
@ -77,6 +83,10 @@ class WebApplicationClient(Client):
|
|||
'https://example.com?client_id=your_id&response_type=code&redirect_uri=https%3A%2F%2Fa.b%2Fcallback'
|
||||
>>> client.prepare_request_uri('https://example.com', scope=['profile', 'pictures'])
|
||||
'https://example.com?client_id=your_id&response_type=code&scope=profile+pictures'
|
||||
>>> client.prepare_request_uri('https://example.com', code_challenge='kjasBS523KdkAILD2k78NdcJSk2k3KHG6')
|
||||
'https://example.com?client_id=your_id&response_type=code&code_challenge=kjasBS523KdkAILD2k78NdcJSk2k3KHG6'
|
||||
>>> client.prepare_request_uri('https://example.com', code_challenge_method='S256')
|
||||
'https://example.com?client_id=your_id&response_type=code&code_challenge_method=S256'
|
||||
>>> client.prepare_request_uri('https://example.com', foo='bar')
|
||||
'https://example.com?client_id=your_id&response_type=code&foo=bar'
|
||||
|
||||
|
@ -86,11 +96,13 @@ class WebApplicationClient(Client):
|
|||
.. _`Section 3.3`: https://tools.ietf.org/html/rfc6749#section-3.3
|
||||
.. _`Section 10.12`: https://tools.ietf.org/html/rfc6749#section-10.12
|
||||
"""
|
||||
scope = self.scope if scope is None else scope
|
||||
return prepare_grant_uri(uri, self.client_id, 'code',
|
||||
redirect_uri=redirect_uri, scope=scope, state=state, **kwargs)
|
||||
redirect_uri=redirect_uri, scope=scope, state=state, code_challenge=code_challenge,
|
||||
code_challenge_method=code_challenge_method, **kwargs)
|
||||
|
||||
def prepare_request_body(self, code=None, redirect_uri=None, body='',
|
||||
include_client_id=True, **kwargs):
|
||||
include_client_id=True, code_verifier=None, **kwargs):
|
||||
"""Prepare the access token request body.
|
||||
|
||||
The client makes a request to the token endpoint by adding the
|
||||
|
@ -105,7 +117,7 @@ class WebApplicationClient(Client):
|
|||
values MUST be identical.
|
||||
|
||||
:param body: Existing request body (URL encoded string) to embed parameters
|
||||
into. This may contain extra paramters. Default ''.
|
||||
into. This may contain extra parameters. Default ''.
|
||||
|
||||
:param include_client_id: `True` (default) to send the `client_id` in the
|
||||
body of the upstream request. This is required
|
||||
|
@ -113,6 +125,9 @@ class WebApplicationClient(Client):
|
|||
authorization server as described in `Section 3.2.1`_.
|
||||
:type include_client_id: Boolean
|
||||
|
||||
:param code_verifier: OPTIONAL. A cryptographically random string that is used to correlate the
|
||||
authorization request to the token request.
|
||||
|
||||
:param kwargs: Extra parameters to include in the token request.
|
||||
|
||||
In addition OAuthLib will add the ``grant_type`` parameter set to
|
||||
|
@ -127,6 +142,8 @@ class WebApplicationClient(Client):
|
|||
>>> client = WebApplicationClient('your_id')
|
||||
>>> client.prepare_request_body(code='sh35ksdf09sf')
|
||||
'grant_type=authorization_code&code=sh35ksdf09sf'
|
||||
>>> client.prepare_request_body(code_verifier='KB46DCKJ873NCGXK5GD682NHDKK34GR')
|
||||
'grant_type=authorization_code&code_verifier=KB46DCKJ873NCGXK5GD682NHDKK34GR'
|
||||
>>> client.prepare_request_body(code='sh35ksdf09sf', foo='bar')
|
||||
'grant_type=authorization_code&code=sh35ksdf09sf&foo=bar'
|
||||
|
||||
|
@ -154,7 +171,7 @@ class WebApplicationClient(Client):
|
|||
kwargs['client_id'] = self.client_id
|
||||
kwargs['include_client_id'] = include_client_id
|
||||
return prepare_token_request(self.grant_type, code=code, body=body,
|
||||
redirect_uri=redirect_uri, **kwargs)
|
||||
redirect_uri=redirect_uri, code_verifier=code_verifier, **kwargs)
|
||||
|
||||
def parse_request_uri_response(self, uri, state=None):
|
||||
"""Parse the URI query for code and state.
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
oauthlib.oauth2.rfc6749
|
||||
~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
@ -6,16 +5,13 @@ oauthlib.oauth2.rfc6749
|
|||
This module is an implementation of various logic needed
|
||||
for consuming and providing OAuth 2.0 RFC6749.
|
||||
"""
|
||||
from __future__ import absolute_import, unicode_literals
|
||||
|
||||
from .authorization import AuthorizationEndpoint
|
||||
from .introspect import IntrospectEndpoint
|
||||
from .metadata import MetadataEndpoint
|
||||
from .token import TokenEndpoint
|
||||
from .pre_configured import (
|
||||
BackendApplicationServer, LegacyApplicationServer, MobileApplicationServer,
|
||||
Server, WebApplicationServer,
|
||||
)
|
||||
from .resource import ResourceEndpoint
|
||||
from .revocation import RevocationEndpoint
|
||||
from .pre_configured import Server
|
||||
from .pre_configured import WebApplicationServer
|
||||
from .pre_configured import MobileApplicationServer
|
||||
from .pre_configured import LegacyApplicationServer
|
||||
from .pre_configured import BackendApplicationServer
|
||||
from .token import TokenEndpoint
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
oauthlib.oauth2.rfc6749
|
||||
~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
@ -6,8 +5,6 @@ oauthlib.oauth2.rfc6749
|
|||
This module is an implementation of various logic needed
|
||||
for consuming and providing OAuth 2.0 RFC6749.
|
||||
"""
|
||||
from __future__ import absolute_import, unicode_literals
|
||||
|
||||
import logging
|
||||
|
||||
from oauthlib.common import Request
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
oauthlib.oauth2.rfc6749
|
||||
~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
@ -6,21 +5,18 @@ oauthlib.oauth2.rfc6749
|
|||
This module is an implementation of various logic needed
|
||||
for consuming and providing OAuth 2.0 RFC6749.
|
||||
"""
|
||||
from __future__ import absolute_import, unicode_literals
|
||||
|
||||
import functools
|
||||
import logging
|
||||
|
||||
from ..errors import (FatalClientError, OAuth2Error, ServerError,
|
||||
TemporarilyUnavailableError, InvalidRequestError,
|
||||
InvalidClientError, UnsupportedTokenTypeError)
|
||||
|
||||
from oauthlib.common import CaseInsensitiveDict, urldecode
|
||||
from ..errors import (
|
||||
FatalClientError, InvalidClientError, InvalidRequestError, OAuth2Error,
|
||||
ServerError, TemporarilyUnavailableError, UnsupportedTokenTypeError,
|
||||
)
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class BaseEndpoint(object):
|
||||
class BaseEndpoint:
|
||||
|
||||
def __init__(self):
|
||||
self._available = True
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
oauthlib.oauth2.rfc6749.endpoint.introspect
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
@ -7,14 +6,12 @@ An implementation of the OAuth 2.0 `Token Introspection`.
|
|||
|
||||
.. _`Token Introspection`: https://tools.ietf.org/html/rfc7662
|
||||
"""
|
||||
from __future__ import absolute_import, unicode_literals
|
||||
|
||||
import json
|
||||
import logging
|
||||
|
||||
from oauthlib.common import Request
|
||||
|
||||
from ..errors import OAuth2Error, UnsupportedTokenTypeError
|
||||
from ..errors import OAuth2Error
|
||||
from .base import BaseEndpoint, catch_errors_and_unavailability
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
@ -89,9 +86,9 @@ class IntrospectEndpoint(BaseEndpoint):
|
|||
an HTTP POST request with parameters sent as
|
||||
"application/x-www-form-urlencoded".
|
||||
|
||||
token REQUIRED. The string value of the token.
|
||||
* token REQUIRED. The string value of the token.
|
||||
* token_type_hint OPTIONAL.
|
||||
|
||||
token_type_hint OPTIONAL.
|
||||
A hint about the type of the token submitted for
|
||||
introspection. The protected resource MAY pass this parameter to
|
||||
help the authorization server optimize the token lookup. If the
|
||||
|
@ -99,11 +96,9 @@ class IntrospectEndpoint(BaseEndpoint):
|
|||
extend its search across all of its supported token types. An
|
||||
authorization server MAY ignore this parameter, particularly if it
|
||||
is able to detect the token type automatically.
|
||||
* access_token: An Access Token as defined in [`RFC6749`],
|
||||
`section 1.4`_
|
||||
|
||||
* refresh_token: A Refresh Token as defined in [`RFC6749`],
|
||||
`section 1.5`_
|
||||
* access_token: An Access Token as defined in [`RFC6749`], `section 1.4`_
|
||||
* refresh_token: A Refresh Token as defined in [`RFC6749`], `section 1.5`_
|
||||
|
||||
The introspection endpoint MAY accept other OPTIONAL
|
||||
parameters to provide further context to the query. For
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
oauthlib.oauth2.rfc6749.endpoint.metadata
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
@ -7,20 +6,16 @@ An implementation of the `OAuth 2.0 Authorization Server Metadata`.
|
|||
|
||||
.. _`OAuth 2.0 Authorization Server Metadata`: https://tools.ietf.org/html/rfc8414
|
||||
"""
|
||||
from __future__ import absolute_import, unicode_literals
|
||||
|
||||
import copy
|
||||
import json
|
||||
import logging
|
||||
|
||||
from ....common import unicode_type
|
||||
from .base import BaseEndpoint, catch_errors_and_unavailability
|
||||
from .. import grant_types, utils
|
||||
from .authorization import AuthorizationEndpoint
|
||||
from .base import BaseEndpoint, catch_errors_and_unavailability
|
||||
from .introspect import IntrospectEndpoint
|
||||
from .token import TokenEndpoint
|
||||
from .revocation import RevocationEndpoint
|
||||
from .. import grant_types
|
||||
|
||||
from .token import TokenEndpoint
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
@ -59,7 +54,8 @@ class MetadataEndpoint(BaseEndpoint):
|
|||
"""Create metadata response
|
||||
"""
|
||||
headers = {
|
||||
'Content-Type': 'application/json'
|
||||
'Content-Type': 'application/json',
|
||||
'Access-Control-Allow-Origin': '*',
|
||||
}
|
||||
return headers, json.dumps(self.claims), 200
|
||||
|
||||
|
@ -72,7 +68,7 @@ class MetadataEndpoint(BaseEndpoint):
|
|||
raise ValueError("key {} is a mandatory metadata.".format(key))
|
||||
|
||||
elif is_issuer:
|
||||
if not array[key].startswith("https"):
|
||||
if not utils.is_secure_transport(array[key]):
|
||||
raise ValueError("key {}: {} must be an HTTPS URL".format(key, array[key]))
|
||||
if "?" in array[key] or "&" in array[key] or "#" in array[key]:
|
||||
raise ValueError("key {}: {} must not contain query or fragment components".format(key, array[key]))
|
||||
|
@ -85,7 +81,7 @@ class MetadataEndpoint(BaseEndpoint):
|
|||
if not isinstance(array[key], list):
|
||||
raise ValueError("key {}: {} must be an Array".format(key, array[key]))
|
||||
for elem in array[key]:
|
||||
if not isinstance(elem, unicode_type):
|
||||
if not isinstance(elem, str):
|
||||
raise ValueError("array {}: {} must contains only string (not {})".format(key, array[key], elem))
|
||||
|
||||
def validate_metadata_token(self, claims, endpoint):
|
||||
|
@ -166,10 +162,10 @@ class MetadataEndpoint(BaseEndpoint):
|
|||
response_types_supported
|
||||
REQUIRED.
|
||||
|
||||
* Other OPTIONAL fields:
|
||||
jwks_uri
|
||||
registration_endpoint
|
||||
response_modes_supported
|
||||
Other OPTIONAL fields:
|
||||
jwks_uri,
|
||||
registration_endpoint,
|
||||
response_modes_supported
|
||||
|
||||
grant_types_supported
|
||||
OPTIONAL. JSON array containing a list of the OAuth 2.0 grant
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
oauthlib.oauth2.rfc6749.endpoints.pre_configured
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
@ -6,13 +5,10 @@ oauthlib.oauth2.rfc6749.endpoints.pre_configured
|
|||
This module is an implementation of various endpoints needed
|
||||
for providing OAuth 2.0 RFC6749 servers.
|
||||
"""
|
||||
from __future__ import absolute_import, unicode_literals
|
||||
|
||||
from ..grant_types import (AuthorizationCodeGrant,
|
||||
ClientCredentialsGrant,
|
||||
ImplicitGrant,
|
||||
RefreshTokenGrant,
|
||||
ResourceOwnerPasswordCredentialsGrant)
|
||||
from ..grant_types import (
|
||||
AuthorizationCodeGrant, ClientCredentialsGrant, ImplicitGrant,
|
||||
RefreshTokenGrant, ResourceOwnerPasswordCredentialsGrant,
|
||||
)
|
||||
from ..tokens import BearerToken
|
||||
from .authorization import AuthorizationEndpoint
|
||||
from .introspect import IntrospectEndpoint
|
||||
|
@ -42,34 +38,34 @@ class Server(AuthorizationEndpoint, IntrospectEndpoint, TokenEndpoint,
|
|||
:param kwargs: Extra parameters to pass to authorization-,
|
||||
token-, resource-, and revocation-endpoint constructors.
|
||||
"""
|
||||
auth_grant = AuthorizationCodeGrant(request_validator)
|
||||
implicit_grant = ImplicitGrant(request_validator)
|
||||
password_grant = ResourceOwnerPasswordCredentialsGrant(
|
||||
self.auth_grant = AuthorizationCodeGrant(request_validator)
|
||||
self.implicit_grant = ImplicitGrant(request_validator)
|
||||
self.password_grant = ResourceOwnerPasswordCredentialsGrant(
|
||||
request_validator)
|
||||
credentials_grant = ClientCredentialsGrant(request_validator)
|
||||
refresh_grant = RefreshTokenGrant(request_validator)
|
||||
self.credentials_grant = ClientCredentialsGrant(request_validator)
|
||||
self.refresh_grant = RefreshTokenGrant(request_validator)
|
||||
|
||||
bearer = BearerToken(request_validator, token_generator,
|
||||
self.bearer = BearerToken(request_validator, token_generator,
|
||||
token_expires_in, refresh_token_generator)
|
||||
|
||||
AuthorizationEndpoint.__init__(self, default_response_type='code',
|
||||
response_types={
|
||||
'code': auth_grant,
|
||||
'token': implicit_grant,
|
||||
'none': auth_grant
|
||||
'code': self.auth_grant,
|
||||
'token': self.implicit_grant,
|
||||
'none': self.auth_grant
|
||||
},
|
||||
default_token_type=bearer)
|
||||
default_token_type=self.bearer)
|
||||
|
||||
TokenEndpoint.__init__(self, default_grant_type='authorization_code',
|
||||
grant_types={
|
||||
'authorization_code': auth_grant,
|
||||
'password': password_grant,
|
||||
'client_credentials': credentials_grant,
|
||||
'refresh_token': refresh_grant,
|
||||
'authorization_code': self.auth_grant,
|
||||
'password': self.password_grant,
|
||||
'client_credentials': self.credentials_grant,
|
||||
'refresh_token': self.refresh_grant,
|
||||
},
|
||||
default_token_type=bearer)
|
||||
default_token_type=self.bearer)
|
||||
ResourceEndpoint.__init__(self, default_token='Bearer',
|
||||
token_types={'Bearer': bearer})
|
||||
token_types={'Bearer': self.bearer})
|
||||
RevocationEndpoint.__init__(self, request_validator)
|
||||
IntrospectEndpoint.__init__(self, request_validator)
|
||||
|
||||
|
@ -94,21 +90,21 @@ class WebApplicationServer(AuthorizationEndpoint, IntrospectEndpoint, TokenEndpo
|
|||
:param kwargs: Extra parameters to pass to authorization-,
|
||||
token-, resource-, and revocation-endpoint constructors.
|
||||
"""
|
||||
auth_grant = AuthorizationCodeGrant(request_validator)
|
||||
refresh_grant = RefreshTokenGrant(request_validator)
|
||||
bearer = BearerToken(request_validator, token_generator,
|
||||
self.auth_grant = AuthorizationCodeGrant(request_validator)
|
||||
self.refresh_grant = RefreshTokenGrant(request_validator)
|
||||
self.bearer = BearerToken(request_validator, token_generator,
|
||||
token_expires_in, refresh_token_generator)
|
||||
AuthorizationEndpoint.__init__(self, default_response_type='code',
|
||||
response_types={'code': auth_grant},
|
||||
default_token_type=bearer)
|
||||
response_types={'code': self.auth_grant},
|
||||
default_token_type=self.bearer)
|
||||
TokenEndpoint.__init__(self, default_grant_type='authorization_code',
|
||||
grant_types={
|
||||
'authorization_code': auth_grant,
|
||||
'refresh_token': refresh_grant,
|
||||
'authorization_code': self.auth_grant,
|
||||
'refresh_token': self.refresh_grant,
|
||||
},
|
||||
default_token_type=bearer)
|
||||
default_token_type=self.bearer)
|
||||
ResourceEndpoint.__init__(self, default_token='Bearer',
|
||||
token_types={'Bearer': bearer})
|
||||
token_types={'Bearer': self.bearer})
|
||||
RevocationEndpoint.__init__(self, request_validator)
|
||||
IntrospectEndpoint.__init__(self, request_validator)
|
||||
|
||||
|
@ -133,15 +129,15 @@ class MobileApplicationServer(AuthorizationEndpoint, IntrospectEndpoint,
|
|||
:param kwargs: Extra parameters to pass to authorization-,
|
||||
token-, resource-, and revocation-endpoint constructors.
|
||||
"""
|
||||
implicit_grant = ImplicitGrant(request_validator)
|
||||
bearer = BearerToken(request_validator, token_generator,
|
||||
self.implicit_grant = ImplicitGrant(request_validator)
|
||||
self.bearer = BearerToken(request_validator, token_generator,
|
||||
token_expires_in, refresh_token_generator)
|
||||
AuthorizationEndpoint.__init__(self, default_response_type='token',
|
||||
response_types={
|
||||
'token': implicit_grant},
|
||||
default_token_type=bearer)
|
||||
'token': self.implicit_grant},
|
||||
default_token_type=self.bearer)
|
||||
ResourceEndpoint.__init__(self, default_token='Bearer',
|
||||
token_types={'Bearer': bearer})
|
||||
token_types={'Bearer': self.bearer})
|
||||
RevocationEndpoint.__init__(self, request_validator,
|
||||
supported_token_types=['access_token'])
|
||||
IntrospectEndpoint.__init__(self, request_validator,
|
||||
|
@ -168,19 +164,19 @@ class LegacyApplicationServer(TokenEndpoint, IntrospectEndpoint,
|
|||
:param kwargs: Extra parameters to pass to authorization-,
|
||||
token-, resource-, and revocation-endpoint constructors.
|
||||
"""
|
||||
password_grant = ResourceOwnerPasswordCredentialsGrant(
|
||||
self.password_grant = ResourceOwnerPasswordCredentialsGrant(
|
||||
request_validator)
|
||||
refresh_grant = RefreshTokenGrant(request_validator)
|
||||
bearer = BearerToken(request_validator, token_generator,
|
||||
self.refresh_grant = RefreshTokenGrant(request_validator)
|
||||
self.bearer = BearerToken(request_validator, token_generator,
|
||||
token_expires_in, refresh_token_generator)
|
||||
TokenEndpoint.__init__(self, default_grant_type='password',
|
||||
grant_types={
|
||||
'password': password_grant,
|
||||
'refresh_token': refresh_grant,
|
||||
'password': self.password_grant,
|
||||
'refresh_token': self.refresh_grant,
|
||||
},
|
||||
default_token_type=bearer)
|
||||
default_token_type=self.bearer)
|
||||
ResourceEndpoint.__init__(self, default_token='Bearer',
|
||||
token_types={'Bearer': bearer})
|
||||
token_types={'Bearer': self.bearer})
|
||||
RevocationEndpoint.__init__(self, request_validator)
|
||||
IntrospectEndpoint.__init__(self, request_validator)
|
||||
|
||||
|
@ -205,15 +201,15 @@ class BackendApplicationServer(TokenEndpoint, IntrospectEndpoint,
|
|||
:param kwargs: Extra parameters to pass to authorization-,
|
||||
token-, resource-, and revocation-endpoint constructors.
|
||||
"""
|
||||
credentials_grant = ClientCredentialsGrant(request_validator)
|
||||
bearer = BearerToken(request_validator, token_generator,
|
||||
self.credentials_grant = ClientCredentialsGrant(request_validator)
|
||||
self.bearer = BearerToken(request_validator, token_generator,
|
||||
token_expires_in, refresh_token_generator)
|
||||
TokenEndpoint.__init__(self, default_grant_type='client_credentials',
|
||||
grant_types={
|
||||
'client_credentials': credentials_grant},
|
||||
default_token_type=bearer)
|
||||
'client_credentials': self.credentials_grant},
|
||||
default_token_type=self.bearer)
|
||||
ResourceEndpoint.__init__(self, default_token='Bearer',
|
||||
token_types={'Bearer': bearer})
|
||||
token_types={'Bearer': self.bearer})
|
||||
RevocationEndpoint.__init__(self, request_validator,
|
||||
supported_token_types=['access_token'])
|
||||
IntrospectEndpoint.__init__(self, request_validator,
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
oauthlib.oauth2.rfc6749
|
||||
~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
@ -6,8 +5,6 @@ oauthlib.oauth2.rfc6749
|
|||
This module is an implementation of various logic needed
|
||||
for consuming and providing OAuth 2.0 RFC6749.
|
||||
"""
|
||||
from __future__ import absolute_import, unicode_literals
|
||||
|
||||
import logging
|
||||
|
||||
from oauthlib.common import Request
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
oauthlib.oauth2.rfc6749.endpoint.revocation
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
@ -7,13 +6,11 @@ An implementation of the OAuth 2 `Token Revocation`_ spec (draft 11).
|
|||
|
||||
.. _`Token Revocation`: https://tools.ietf.org/html/draft-ietf-oauth-revocation-11
|
||||
"""
|
||||
from __future__ import absolute_import, unicode_literals
|
||||
|
||||
import logging
|
||||
|
||||
from oauthlib.common import Request
|
||||
|
||||
from ..errors import OAuth2Error, UnsupportedTokenTypeError
|
||||
from ..errors import OAuth2Error
|
||||
from .base import BaseEndpoint, catch_errors_and_unavailability
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
@ -45,7 +42,7 @@ class RevocationEndpoint(BaseEndpoint):
|
|||
|
||||
|
||||
The authorization server responds with HTTP status code 200 if the
|
||||
token has been revoked sucessfully or if the client submitted an
|
||||
token has been revoked successfully or if the client submitted an
|
||||
invalid token.
|
||||
|
||||
Note: invalid tokens do not cause an error response since the client
|
||||
|
@ -73,7 +70,7 @@ class RevocationEndpoint(BaseEndpoint):
|
|||
log.debug('Client error during validation of %r. %r.', request, e)
|
||||
response_body = e.json
|
||||
if self.enable_jsonp and request.callback:
|
||||
response_body = '%s(%s);' % (request.callback, response_body)
|
||||
response_body = '{}({});'.format(request.callback, response_body)
|
||||
resp_headers.update(e.headers)
|
||||
return resp_headers, response_body, e.status_code
|
||||
|
||||
|
@ -98,7 +95,7 @@ class RevocationEndpoint(BaseEndpoint):
|
|||
submitted for revocation. Clients MAY pass this parameter in order to
|
||||
help the authorization server to optimize the token lookup. If the
|
||||
server is unable to locate the token using the given hint, it MUST
|
||||
extend its search accross all of its supported token types. An
|
||||
extend its search across all of its supported token types. An
|
||||
authorization server MAY ignore this parameter, particularly if it is
|
||||
able to detect the token type automatically. This specification
|
||||
defines two such values:
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
oauthlib.oauth2.rfc6749
|
||||
~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
@ -6,8 +5,6 @@ oauthlib.oauth2.rfc6749
|
|||
This module is an implementation of various logic needed
|
||||
for consuming and providing OAuth 2.0 RFC6749.
|
||||
"""
|
||||
from __future__ import absolute_import, unicode_literals
|
||||
|
||||
import logging
|
||||
|
||||
from oauthlib.common import Request
|
||||
|
@ -39,7 +36,6 @@ class TokenEndpoint(BaseEndpoint):
|
|||
https://example.com/path?query=component # OK
|
||||
https://example.com/path?query=component#fragment # Not OK
|
||||
|
||||
Since requests to the authorization endpoint result in user
|
||||
Since requests to the token endpoint result in the transmission of
|
||||
clear-text credentials (in the HTTP request and response), the
|
||||
authorization server MUST require the use of TLS as described in
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
# coding=utf-8
|
||||
"""
|
||||
oauthlib.oauth2.rfc6749.errors
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
@ -6,8 +5,6 @@ oauthlib.oauth2.rfc6749.errors
|
|||
Error used both by OAuth 2 clients and providers to represent the spec
|
||||
defined error responses for all four core grant types.
|
||||
"""
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import json
|
||||
|
||||
from oauthlib.common import add_params_to_uri, urlencode
|
||||
|
@ -45,10 +42,10 @@ class OAuth2Error(Exception):
|
|||
if description is not None:
|
||||
self.description = description
|
||||
|
||||
message = '(%s) %s' % (self.error, self.description)
|
||||
message = '({}) {}'.format(self.error, self.description)
|
||||
if request:
|
||||
message += ' ' + repr(request)
|
||||
super(OAuth2Error, self).__init__(message)
|
||||
super().__init__(message)
|
||||
|
||||
self.uri = uri
|
||||
self.state = state
|
||||
|
@ -106,15 +103,12 @@ class OAuth2Error(Exception):
|
|||
value "Bearer". This scheme MUST be followed by one or more
|
||||
auth-param values.
|
||||
"""
|
||||
authvalues = [
|
||||
"Bearer",
|
||||
'error="{}"'.format(self.error)
|
||||
]
|
||||
authvalues = ['error="{}"'.format(self.error)]
|
||||
if self.description:
|
||||
authvalues.append('error_description="{}"'.format(self.description))
|
||||
if self.uri:
|
||||
authvalues.append('error_uri="{}"'.format(self.uri))
|
||||
return {"WWW-Authenticate": ", ".join(authvalues)}
|
||||
return {"WWW-Authenticate": "Bearer " + ", ".join(authvalues)}
|
||||
return {}
|
||||
|
||||
|
||||
|
@ -389,7 +383,7 @@ class CustomOAuth2Error(OAuth2Error):
|
|||
"""
|
||||
def __init__(self, error, *args, **kwargs):
|
||||
self.error = error
|
||||
super(CustomOAuth2Error, self).__init__(*args, **kwargs)
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
|
||||
def raise_from_error(error, params=None):
|
||||
|
|
|
@ -1,12 +1,11 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
oauthlib.oauth2.rfc6749.grant_types
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
"""
|
||||
from __future__ import unicode_literals, absolute_import
|
||||
|
||||
from .authorization_code import AuthorizationCodeGrant
|
||||
from .implicit import ImplicitGrant
|
||||
from .resource_owner_password_credentials import ResourceOwnerPasswordCredentialsGrant
|
||||
from .client_credentials import ClientCredentialsGrant
|
||||
from .implicit import ImplicitGrant
|
||||
from .refresh_token import RefreshTokenGrant
|
||||
from .resource_owner_password_credentials import (
|
||||
ResourceOwnerPasswordCredentialsGrant,
|
||||
)
|
||||
|
|
|
@ -1,10 +1,7 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
oauthlib.oauth2.rfc6749.grant_types
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
"""
|
||||
from __future__ import absolute_import, unicode_literals
|
||||
|
||||
import base64
|
||||
import hashlib
|
||||
import json
|
||||
|
@ -275,6 +272,8 @@ class AuthorizationCodeGrant(GrantTypeBase):
|
|||
grant = self.create_authorization_code(request)
|
||||
for modifier in self._code_modifiers:
|
||||
grant = modifier(grant, token_handler, request)
|
||||
if 'access_token' in grant:
|
||||
self.request_validator.save_token(grant, request)
|
||||
log.debug('Saving grant %r for %r.', grant, request)
|
||||
self.request_validator.save_authorization_code(
|
||||
request.client_id, grant, request)
|
||||
|
@ -313,6 +312,7 @@ class AuthorizationCodeGrant(GrantTypeBase):
|
|||
self.request_validator.save_token(token, request)
|
||||
self.request_validator.invalidate_authorization_code(
|
||||
request.client_id, request.code, request)
|
||||
headers.update(self._create_cors_headers(request))
|
||||
return headers, json.dumps(token), 200
|
||||
|
||||
def validate_authorization_request(self, request):
|
||||
|
|
|
@ -1,23 +1,21 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
oauthlib.oauth2.rfc6749.grant_types
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
"""
|
||||
from __future__ import absolute_import, unicode_literals
|
||||
|
||||
import logging
|
||||
from itertools import chain
|
||||
|
||||
from oauthlib.common import add_params_to_uri
|
||||
from oauthlib.uri_validate import is_absolute_uri
|
||||
from oauthlib.oauth2.rfc6749 import errors, utils
|
||||
from oauthlib.uri_validate import is_absolute_uri
|
||||
|
||||
from ..request_validator import RequestValidator
|
||||
from ..utils import is_secure_transport
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ValidatorsContainer(object):
|
||||
class ValidatorsContainer:
|
||||
"""
|
||||
Container object for holding custom validator callables to be invoked
|
||||
as part of the grant type `validate_authorization_request()` or
|
||||
|
@ -74,7 +72,7 @@ class ValidatorsContainer(object):
|
|||
return chain(self.post_auth, self.post_token)
|
||||
|
||||
|
||||
class GrantTypeBase(object):
|
||||
class GrantTypeBase:
|
||||
error_uri = None
|
||||
request_validator = None
|
||||
default_response_mode = 'fragment'
|
||||
|
@ -251,3 +249,20 @@ class GrantTypeBase(object):
|
|||
raise errors.MissingRedirectURIError(request=request)
|
||||
if not is_absolute_uri(request.redirect_uri):
|
||||
raise errors.InvalidRedirectURIError(request=request)
|
||||
|
||||
def _create_cors_headers(self, request):
|
||||
"""If CORS is allowed, create the appropriate headers."""
|
||||
if 'origin' not in request.headers:
|
||||
return {}
|
||||
|
||||
origin = request.headers['origin']
|
||||
if not is_secure_transport(origin):
|
||||
log.debug('Origin "%s" is not HTTPS, CORS not allowed.', origin)
|
||||
return {}
|
||||
elif not self.request_validator.is_origin_allowed(
|
||||
request.client_id, origin, request):
|
||||
log.debug('Invalid origin "%s", CORS not allowed.', origin)
|
||||
return {}
|
||||
else:
|
||||
log.debug('Valid origin "%s", injecting CORS headers.', origin)
|
||||
return {'Access-Control-Allow-Origin': origin}
|
||||
|
|
|
@ -1,15 +1,11 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
oauthlib.oauth2.rfc6749.grant_types
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
"""
|
||||
from __future__ import absolute_import, unicode_literals
|
||||
|
||||
import json
|
||||
import logging
|
||||
|
||||
from .. import errors
|
||||
from ..request_validator import RequestValidator
|
||||
from .base import GrantTypeBase
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
@ -119,8 +115,8 @@ class ClientCredentialsGrant(GrantTypeBase):
|
|||
# Ensure client is authorized use of this grant type
|
||||
self.validate_grant_type(request)
|
||||
|
||||
log.debug('Authorizing access to user %r.', request.user)
|
||||
request.client_id = request.client_id or request.client.client_id
|
||||
log.debug('Authorizing access to client %r.', request.client_id)
|
||||
self.validate_scopes(request)
|
||||
|
||||
for validator in self.custom_validators.post_token:
|
||||
|
|
|
@ -1,10 +1,7 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
oauthlib.oauth2.rfc6749.grant_types
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
"""
|
||||
from __future__ import absolute_import, unicode_literals
|
||||
|
||||
import logging
|
||||
|
||||
from oauthlib import common
|
||||
|
|
|
@ -1,15 +1,11 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
oauthlib.oauth2.rfc6749.grant_types
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
"""
|
||||
from __future__ import absolute_import, unicode_literals
|
||||
|
||||
import json
|
||||
import logging
|
||||
|
||||
from .. import errors, utils
|
||||
from ..request_validator import RequestValidator
|
||||
from .base import GrantTypeBase
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
@ -25,7 +21,7 @@ class RefreshTokenGrant(GrantTypeBase):
|
|||
def __init__(self, request_validator=None,
|
||||
issue_new_refresh_tokens=True,
|
||||
**kwargs):
|
||||
super(RefreshTokenGrant, self).__init__(
|
||||
super().__init__(
|
||||
request_validator,
|
||||
issue_new_refresh_tokens=issue_new_refresh_tokens,
|
||||
**kwargs)
|
||||
|
@ -67,12 +63,13 @@ class RefreshTokenGrant(GrantTypeBase):
|
|||
refresh_token=self.issue_new_refresh_tokens)
|
||||
|
||||
for modifier in self._token_modifiers:
|
||||
token = modifier(token)
|
||||
token = modifier(token, token_handler, request)
|
||||
|
||||
self.request_validator.save_token(token, request)
|
||||
|
||||
log.debug('Issuing new token to client id %r (%r), %r.',
|
||||
request.client_id, request.client, token)
|
||||
headers.update(self._create_cors_headers(request))
|
||||
return headers, json.dumps(token), 200
|
||||
|
||||
def validate_token_request(self, request):
|
||||
|
@ -126,7 +123,7 @@ class RefreshTokenGrant(GrantTypeBase):
|
|||
|
||||
if request.scope:
|
||||
request.scopes = utils.scope_to_list(request.scope)
|
||||
if (not all((s in original_scopes for s in request.scopes))
|
||||
if (not all(s in original_scopes for s in request.scopes)
|
||||
and not self.request_validator.is_within_original_scope(
|
||||
request.scopes, request.refresh_token, request)):
|
||||
log.debug('Refresh token %s lack requested scopes, %r.',
|
||||
|
|
|
@ -1,15 +1,11 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
oauthlib.oauth2.rfc6749.grant_types
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
"""
|
||||
from __future__ import absolute_import, unicode_literals
|
||||
|
||||
import json
|
||||
import logging
|
||||
|
||||
from .. import errors
|
||||
from ..request_validator import RequestValidator
|
||||
from .base import GrantTypeBase
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
oauthlib.oauth2.rfc6749.parameters
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
@ -7,29 +6,24 @@ This module contains methods related to `Section 4`_ of the OAuth 2 RFC.
|
|||
|
||||
.. _`Section 4`: https://tools.ietf.org/html/rfc6749#section-4
|
||||
"""
|
||||
from __future__ import absolute_import, unicode_literals
|
||||
|
||||
import json
|
||||
import os
|
||||
import time
|
||||
import urllib.parse as urlparse
|
||||
|
||||
from oauthlib.common import add_params_to_qs, add_params_to_uri, unicode_type
|
||||
from oauthlib.common import add_params_to_qs, add_params_to_uri
|
||||
from oauthlib.signals import scope_changed
|
||||
|
||||
from .errors import (InsecureTransportError, MismatchingStateError,
|
||||
MissingCodeError, MissingTokenError,
|
||||
MissingTokenTypeError, raise_from_error)
|
||||
from .errors import (
|
||||
InsecureTransportError, MismatchingStateError, MissingCodeError,
|
||||
MissingTokenError, MissingTokenTypeError, raise_from_error,
|
||||
)
|
||||
from .tokens import OAuth2Token
|
||||
from .utils import is_secure_transport, list_to_scope, scope_to_list
|
||||
|
||||
try:
|
||||
import urlparse
|
||||
except ImportError:
|
||||
import urllib.parse as urlparse
|
||||
|
||||
|
||||
def prepare_grant_uri(uri, client_id, response_type, redirect_uri=None,
|
||||
scope=None, state=None, **kwargs):
|
||||
scope=None, state=None, code_challenge=None, code_challenge_method='plain', **kwargs):
|
||||
"""Prepare the authorization grant request URI.
|
||||
|
||||
The client constructs the request URI by adding the following
|
||||
|
@ -51,6 +45,11 @@ def prepare_grant_uri(uri, client_id, response_type, redirect_uri=None,
|
|||
back to the client. The parameter SHOULD be used for
|
||||
preventing cross-site request forgery as described in
|
||||
`Section 10.12`_.
|
||||
:param code_challenge: PKCE parameter. A challenge derived from the
|
||||
code_verifier that is sent in the authorization
|
||||
request, to be verified against later.
|
||||
:param code_challenge_method: PKCE parameter. A method that was used to derive the
|
||||
code_challenge. Defaults to "plain" if not present in the request.
|
||||
:param kwargs: Extra arguments to embed in the grant/authorization URL.
|
||||
|
||||
An example of an authorization code grant authorization URL:
|
||||
|
@ -58,6 +57,7 @@ def prepare_grant_uri(uri, client_id, response_type, redirect_uri=None,
|
|||
.. code-block:: http
|
||||
|
||||
GET /authorize?response_type=code&client_id=s6BhdRkqt3&state=xyz
|
||||
&code_challenge=kjasBS523KdkAILD2k78NdcJSk2k3KHG6&code_challenge_method=S256
|
||||
&redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb HTTP/1.1
|
||||
Host: server.example.com
|
||||
|
||||
|
@ -79,15 +79,18 @@ def prepare_grant_uri(uri, client_id, response_type, redirect_uri=None,
|
|||
params.append(('scope', list_to_scope(scope)))
|
||||
if state:
|
||||
params.append(('state', state))
|
||||
if code_challenge is not None:
|
||||
params.append(('code_challenge', code_challenge))
|
||||
params.append(('code_challenge_method', code_challenge_method))
|
||||
|
||||
for k in kwargs:
|
||||
if kwargs[k]:
|
||||
params.append((unicode_type(k), kwargs[k]))
|
||||
params.append((str(k), kwargs[k]))
|
||||
|
||||
return add_params_to_uri(uri, params)
|
||||
|
||||
|
||||
def prepare_token_request(grant_type, body='', include_client_id=True, **kwargs):
|
||||
def prepare_token_request(grant_type, body='', include_client_id=True, code_verifier=None, **kwargs):
|
||||
"""Prepare the access token request.
|
||||
|
||||
The client makes a request to the token endpoint by adding the
|
||||
|
@ -122,6 +125,9 @@ def prepare_token_request(grant_type, body='', include_client_id=True, **kwargs)
|
|||
authorization request as described in
|
||||
`Section 4.1.1`_, and their values MUST be identical. *
|
||||
|
||||
:param code_verifier: PKCE parameter. A cryptographically random string that is used to correlate the
|
||||
authorization request to the token request.
|
||||
|
||||
:param kwargs: Extra arguments to embed in the request body.
|
||||
|
||||
Parameters marked with a `*` above are not explicit arguments in the
|
||||
|
@ -146,18 +152,22 @@ def prepare_token_request(grant_type, body='', include_client_id=True, **kwargs)
|
|||
client_id = kwargs.pop('client_id', None)
|
||||
if include_client_id:
|
||||
if client_id is not None:
|
||||
params.append((unicode_type('client_id'), client_id))
|
||||
params.append(('client_id', client_id))
|
||||
|
||||
# use code_verifier if code_challenge was passed in the authorization request
|
||||
if code_verifier is not None:
|
||||
params.append(('code_verifier', code_verifier))
|
||||
|
||||
# the kwargs iteration below only supports including boolean truth (truthy)
|
||||
# values, but some servers may require an empty string for `client_secret`
|
||||
client_secret = kwargs.pop('client_secret', None)
|
||||
if client_secret is not None:
|
||||
params.append((unicode_type('client_secret'), client_secret))
|
||||
params.append(('client_secret', client_secret))
|
||||
|
||||
# this handles: `code`, `redirect_uri`, and other undocumented params
|
||||
for k in kwargs:
|
||||
if kwargs[k]:
|
||||
params.append((unicode_type(k), kwargs[k]))
|
||||
params.append((str(k), kwargs[k]))
|
||||
|
||||
return add_params_to_qs(body, params)
|
||||
|
||||
|
@ -167,7 +177,7 @@ def prepare_token_revocation_request(url, token, token_type_hint="access_token",
|
|||
"""Prepare a token revocation request.
|
||||
|
||||
The client constructs the request by including the following parameters
|
||||
using the "application/x-www-form-urlencoded" format in the HTTP request
|
||||
using the ``application/x-www-form-urlencoded`` format in the HTTP request
|
||||
entity-body:
|
||||
|
||||
:param token: REQUIRED. The token that the client wants to get revoked.
|
||||
|
@ -209,7 +219,7 @@ def prepare_token_revocation_request(url, token, token_type_hint="access_token",
|
|||
|
||||
for k in kwargs:
|
||||
if kwargs[k]:
|
||||
params.append((unicode_type(k), kwargs[k]))
|
||||
params.append((str(k), kwargs[k]))
|
||||
|
||||
headers = {'Content-Type': 'application/x-www-form-urlencoded'}
|
||||
|
||||
|
@ -433,7 +443,7 @@ def parse_token_response(body, scope=None):
|
|||
|
||||
|
||||
def validate_token_parameters(params):
|
||||
"""Ensures token precence, token type, expiration and scope in params."""
|
||||
"""Ensures token presence, token type, expiration and scope in params."""
|
||||
if 'error' in params:
|
||||
raise_from_error(params.get('error'), params)
|
||||
|
||||
|
|
|
@ -1,16 +1,13 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
oauthlib.oauth2.rfc6749.request_validator
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
"""
|
||||
from __future__ import absolute_import, unicode_literals
|
||||
|
||||
import logging
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class RequestValidator(object):
|
||||
class RequestValidator:
|
||||
|
||||
def client_authentication_required(self, request, *args, **kwargs):
|
||||
"""Determine if client authentication is required for current request.
|
||||
|
@ -51,6 +48,17 @@ class RequestValidator(object):
|
|||
Headers may be accesses through request.headers and parameters found in
|
||||
both body and query can be obtained by direct attribute access, i.e.
|
||||
request.client_id for client_id in the URL query.
|
||||
|
||||
The authentication process is required to contain the identification of
|
||||
the client (i.e. search the database based on the client_id). In case the
|
||||
client doesn't exist based on the received client_id, this method has to
|
||||
return False and the HTTP response created by the library will contain
|
||||
'invalid_client' message.
|
||||
|
||||
After the client identification succeeds, this method needs to set the
|
||||
client on the request, i.e. request.client = client. A client object's
|
||||
class must contain the 'client_id' attribute and the 'client_id' must have
|
||||
a value.
|
||||
|
||||
:param request: OAuthlib request.
|
||||
:type request: oauthlib.common.Request
|
||||
|
@ -183,6 +191,7 @@ class RequestValidator(object):
|
|||
claims associated, or `None` in case the token is unknown.
|
||||
|
||||
Below the list of registered claims you should be interested in:
|
||||
|
||||
- scope : space-separated list of scopes
|
||||
- client_id : client identifier
|
||||
- username : human-readable identifier for the resource owner
|
||||
|
@ -196,10 +205,10 @@ class RequestValidator(object):
|
|||
- jti : string identifier for the token
|
||||
|
||||
Note that most of them are coming directly from JWT RFC. More details
|
||||
can be found in `Introspect Claims`_ or `_JWT Claims`_.
|
||||
can be found in `Introspect Claims`_ or `JWT Claims`_.
|
||||
|
||||
The implementation can use *token_type_hint* to improve lookup
|
||||
efficency, but must fallback to other types to be compliant with RFC.
|
||||
efficiency, but must fallback to other types to be compliant with RFC.
|
||||
|
||||
The dict of claims is added to request.token after this method.
|
||||
|
||||
|
@ -435,6 +444,7 @@ class RequestValidator(object):
|
|||
- request.user
|
||||
- request.scopes
|
||||
- request.claims (if given)
|
||||
|
||||
OBS! The request.user attribute should be set to the resource owner
|
||||
associated with this authorization code. Similarly request.scopes
|
||||
must also be set.
|
||||
|
@ -443,6 +453,7 @@ class RequestValidator(object):
|
|||
|
||||
If PKCE is enabled (see 'is_pkce_required' and 'save_authorization_code')
|
||||
you MUST set the following based on the information stored:
|
||||
|
||||
- request.code_challenge
|
||||
- request.code_challenge_method
|
||||
|
||||
|
@ -553,7 +564,7 @@ class RequestValidator(object):
|
|||
OBS! The validation should also set the user attribute of the request
|
||||
to a valid resource owner, i.e. request.user = username or similar. If
|
||||
not set you will be unable to associate a token with a user in the
|
||||
persistance method used (commonly, save_bearer_token).
|
||||
persistence method used (commonly, save_bearer_token).
|
||||
|
||||
:param username: Unicode username.
|
||||
:param password: Unicode password.
|
||||
|
@ -641,3 +652,29 @@ class RequestValidator(object):
|
|||
|
||||
"""
|
||||
raise NotImplementedError('Subclasses must implement this method.')
|
||||
|
||||
def is_origin_allowed(self, client_id, origin, request, *args, **kwargs):
|
||||
"""Indicate if the given origin is allowed to access the token endpoint
|
||||
via Cross-Origin Resource Sharing (CORS). CORS is used by browser-based
|
||||
clients, such as Single-Page Applications, to perform the Authorization
|
||||
Code Grant.
|
||||
|
||||
(Note: If performing Authorization Code Grant via a public client such
|
||||
as a browser, you should use PKCE as well.)
|
||||
|
||||
If this method returns true, the appropriate CORS headers will be added
|
||||
to the response. By default this method always returns False, meaning
|
||||
CORS is disabled.
|
||||
|
||||
:param client_id: Unicode client identifier.
|
||||
:param redirect_uri: Unicode origin.
|
||||
:param request: OAuthlib request.
|
||||
:type request: oauthlib.common.Request
|
||||
:rtype: bool
|
||||
|
||||
Method is used by:
|
||||
- Authorization Code Grant
|
||||
- Refresh Token Grant
|
||||
|
||||
"""
|
||||
return False
|
||||
|
|
|
@ -7,28 +7,22 @@ This module contains methods for adding two types of access tokens to requests.
|
|||
- Bearer https://tools.ietf.org/html/rfc6750
|
||||
- MAC https://tools.ietf.org/html/draft-ietf-oauth-v2-http-mac-01
|
||||
"""
|
||||
from __future__ import absolute_import, unicode_literals
|
||||
|
||||
import hashlib
|
||||
import hmac
|
||||
from binascii import b2a_base64
|
||||
import warnings
|
||||
from binascii import b2a_base64
|
||||
from urllib.parse import urlparse
|
||||
|
||||
from oauthlib import common
|
||||
from oauthlib.common import add_params_to_qs, add_params_to_uri, unicode_type
|
||||
from oauthlib.common import add_params_to_qs, add_params_to_uri
|
||||
|
||||
from . import utils
|
||||
|
||||
try:
|
||||
from urlparse import urlparse
|
||||
except ImportError:
|
||||
from urllib.parse import urlparse
|
||||
|
||||
|
||||
class OAuth2Token(dict):
|
||||
|
||||
def __init__(self, params, old_scope=None):
|
||||
super(OAuth2Token, self).__init__(params)
|
||||
super().__init__(params)
|
||||
self._new_scope = None
|
||||
if 'scope' in params and params['scope']:
|
||||
self._new_scope = set(utils.scope_to_list(params['scope']))
|
||||
|
@ -121,7 +115,7 @@ def prepare_mac_header(token, uri, key, http_method,
|
|||
raise ValueError('unknown hash algorithm')
|
||||
|
||||
if draft == 0:
|
||||
nonce = nonce or '{0}:{1}'.format(utils.generate_age(issue_time),
|
||||
nonce = nonce or '{}:{}'.format(utils.generate_age(issue_time),
|
||||
common.generate_nonce())
|
||||
else:
|
||||
ts = common.generate_timestamp()
|
||||
|
@ -158,7 +152,7 @@ def prepare_mac_header(token, uri, key, http_method,
|
|||
base_string = '\n'.join(base) + '\n'
|
||||
|
||||
# hmac struggles with unicode strings - http://bugs.python.org/issue5285
|
||||
if isinstance(key, unicode_type):
|
||||
if isinstance(key, str):
|
||||
key = key.encode('utf-8')
|
||||
sign = hmac.new(key, base_string.encode('utf-8'), h)
|
||||
sign = b2a_base64(sign.digest())[:-1].decode('utf-8')
|
||||
|
@ -262,7 +256,8 @@ def get_token_from_header(request):
|
|||
return token
|
||||
|
||||
|
||||
class TokenBase(object):
|
||||
class TokenBase:
|
||||
__slots__ = ()
|
||||
|
||||
def __call__(self, request, refresh_token=False):
|
||||
raise NotImplementedError('Subclasses must implement this method.')
|
||||
|
|
|
@ -1,33 +1,22 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
oauthlib.utils
|
||||
~~~~~~~~~~~~~~
|
||||
|
||||
This module contains utility methods used by various parts of the OAuth 2 spec.
|
||||
"""
|
||||
from __future__ import absolute_import, unicode_literals
|
||||
|
||||
import datetime
|
||||
import os
|
||||
from urllib.parse import quote, urlparse
|
||||
|
||||
from oauthlib.common import unicode_type, urldecode
|
||||
|
||||
try:
|
||||
from urllib import quote
|
||||
except ImportError:
|
||||
from urllib.parse import quote
|
||||
try:
|
||||
from urlparse import urlparse
|
||||
except ImportError:
|
||||
from urllib.parse import urlparse
|
||||
from oauthlib.common import urldecode
|
||||
|
||||
|
||||
def list_to_scope(scope):
|
||||
"""Convert a list of scopes to a space separated string."""
|
||||
if isinstance(scope, unicode_type) or scope is None:
|
||||
if isinstance(scope, str) or scope is None:
|
||||
return scope
|
||||
elif isinstance(scope, (set, tuple, list)):
|
||||
return " ".join([unicode_type(s) for s in scope])
|
||||
return " ".join([str(s) for s in scope])
|
||||
else:
|
||||
raise ValueError("Invalid scope (%s), must be string, tuple, set, or list." % scope)
|
||||
|
||||
|
@ -35,7 +24,7 @@ def list_to_scope(scope):
|
|||
def scope_to_list(scope):
|
||||
"""Convert a space separated string to a list of scopes."""
|
||||
if isinstance(scope, (tuple, list, set)):
|
||||
return [unicode_type(s) for s in scope]
|
||||
return [str(s) for s in scope]
|
||||
elif scope is None:
|
||||
return None
|
||||
else:
|
||||
|
@ -74,7 +63,7 @@ def escape(u):
|
|||
TODO: verify whether this can in fact be used for OAuth 2
|
||||
|
||||
"""
|
||||
if not isinstance(u, unicode_type):
|
||||
if not isinstance(u, str):
|
||||
raise ValueError('Only unicode objects are escapable.')
|
||||
return quote(u.encode('utf-8'), safe=b'~')
|
||||
|
||||
|
@ -84,7 +73,7 @@ def generate_age(issue_time):
|
|||
td = datetime.datetime.now() - issue_time
|
||||
age = (td.microseconds + (td.seconds + td.days * 24 * 3600)
|
||||
* 10 ** 6) / 10 ** 6
|
||||
return unicode_type(age)
|
||||
return str(age)
|
||||
|
||||
|
||||
def is_secure_transport(uri):
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
"""
|
||||
oauthlib.oauth2.rfc8628
|
||||
~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
This module is an implementation of various logic needed
|
||||
for consuming and providing OAuth 2.0 Device Authorization RFC8628.
|
||||
"""
|
||||
import logging
|
||||
|
||||
log = logging.getLogger(__name__)
|
|
@ -0,0 +1,8 @@
|
|||
"""
|
||||
oauthlib.oauth2.rfc8628
|
||||
~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
This module is an implementation of various logic needed
|
||||
for consuming OAuth 2.0 Device Authorization RFC8628.
|
||||
"""
|
||||
from .device import DeviceClient
|
|
@ -0,0 +1,95 @@
|
|||
"""
|
||||
oauthlib.oauth2.rfc8628
|
||||
~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
This module is an implementation of various logic needed
|
||||
for consuming and providing OAuth 2.0 Device Authorization RFC8628.
|
||||
"""
|
||||
from oauthlib.common import add_params_to_uri
|
||||
from oauthlib.oauth2 import BackendApplicationClient, Client
|
||||
from oauthlib.oauth2.rfc6749.errors import InsecureTransportError
|
||||
from oauthlib.oauth2.rfc6749.parameters import prepare_token_request
|
||||
from oauthlib.oauth2.rfc6749.utils import is_secure_transport, list_to_scope
|
||||
|
||||
|
||||
class DeviceClient(Client):
|
||||
|
||||
"""A public client utilizing the device authorization workflow.
|
||||
|
||||
The client can request an access token using a device code and
|
||||
a public client id associated with the device code as defined
|
||||
in RFC8628.
|
||||
|
||||
The device authorization grant type can be used to obtain both
|
||||
access tokens and refresh tokens and is intended to be used in
|
||||
a scenario where the device being authorized does not have a
|
||||
user interface that is suitable for performing authentication.
|
||||
"""
|
||||
|
||||
grant_type = 'urn:ietf:params:oauth:grant-type:device_code'
|
||||
|
||||
def __init__(self, client_id, **kwargs):
|
||||
super().__init__(client_id, **kwargs)
|
||||
self.client_secret = kwargs.get('client_secret')
|
||||
|
||||
def prepare_request_uri(self, uri, scope=None, **kwargs):
|
||||
if not is_secure_transport(uri):
|
||||
raise InsecureTransportError()
|
||||
|
||||
scope = self.scope if scope is None else scope
|
||||
params = [(('client_id', self.client_id)), (('grant_type', self.grant_type))]
|
||||
|
||||
if self.client_secret is not None:
|
||||
params.append(('client_secret', self.client_secret))
|
||||
|
||||
if scope:
|
||||
params.append(('scope', list_to_scope(scope)))
|
||||
|
||||
for k in kwargs:
|
||||
if kwargs[k]:
|
||||
params.append((str(k), kwargs[k]))
|
||||
|
||||
return add_params_to_uri(uri, params)
|
||||
|
||||
def prepare_request_body(self, device_code, body='', scope=None,
|
||||
include_client_id=False, **kwargs):
|
||||
"""Add device_code to request body
|
||||
|
||||
The client makes a request to the token endpoint by adding the
|
||||
device_code as a parameter using the
|
||||
"application/x-www-form-urlencoded" format to the HTTP request
|
||||
body.
|
||||
|
||||
:param body: Existing request body (URL encoded string) to embed parameters
|
||||
into. This may contain extra parameters. Default ''.
|
||||
:param scope: The scope of the access request as described by
|
||||
`Section 3.3`_.
|
||||
|
||||
:param include_client_id: `True` to send the `client_id` in the
|
||||
body of the upstream request. This is required
|
||||
if the client is not authenticating with the
|
||||
authorization server as described in
|
||||
`Section 3.2.1`_. False otherwise (default).
|
||||
:type include_client_id: Boolean
|
||||
|
||||
:param kwargs: Extra credentials to include in the token request.
|
||||
|
||||
The prepared body will include all provided device_code as well as
|
||||
the ``grant_type`` parameter set to
|
||||
``urn:ietf:params:oauth:grant-type:device_code``::
|
||||
|
||||
>>> from oauthlib.oauth2 import DeviceClient
|
||||
>>> client = DeviceClient('your_id', 'your_code')
|
||||
>>> client.prepare_request_body(scope=['hello', 'world'])
|
||||
'grant_type=urn:ietf:params:oauth:grant-type:device_code&scope=hello+world'
|
||||
|
||||
.. _`Section 3.2.1`: https://datatracker.ietf.org/doc/html/rfc6749#section-3.2.1
|
||||
.. _`Section 3.3`: https://datatracker.ietf.org/doc/html/rfc6749#section-3.3
|
||||
.. _`Section 3.4`: https://datatracker.ietf.org/doc/html/rfc8628#section-3.4
|
||||
"""
|
||||
|
||||
kwargs['client_id'] = self.client_id
|
||||
kwargs['include_client_id'] = include_client_id
|
||||
scope = self.scope if scope is None else scope
|
||||
return prepare_token_request(self.grant_type, body=body, device_code=device_code,
|
||||
scope=scope, **kwargs)
|
|
@ -1,11 +1,7 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
oauthlib.openid
|
||||
~~~~~~~~~~~~~~
|
||||
|
||||
"""
|
||||
from __future__ import absolute_import, unicode_literals
|
||||
|
||||
from .connect.core.endpoints import Server
|
||||
from .connect.core.endpoints import UserInfoEndpoint
|
||||
from .connect.core.endpoints import Server, UserInfoEndpoint
|
||||
from .connect.core.request_validator import RequestValidator
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
oauthlib.oopenid.core
|
||||
~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
@ -6,7 +5,5 @@ oauthlib.oopenid.core
|
|||
This module is an implementation of various logic needed
|
||||
for consuming and providing OpenID Connect
|
||||
"""
|
||||
from __future__ import absolute_import, unicode_literals
|
||||
|
||||
from .pre_configured import Server
|
||||
from .userinfo import UserInfoEndpoint
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
oauthlib.openid.connect.core.endpoints.pre_configured
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
@ -6,32 +5,21 @@ oauthlib.openid.connect.core.endpoints.pre_configured
|
|||
This module is an implementation of various endpoints needed
|
||||
for providing OpenID Connect servers.
|
||||
"""
|
||||
from __future__ import absolute_import, unicode_literals
|
||||
|
||||
from oauthlib.oauth2.rfc6749.endpoints import (
|
||||
AuthorizationEndpoint,
|
||||
IntrospectEndpoint,
|
||||
ResourceEndpoint,
|
||||
RevocationEndpoint,
|
||||
TokenEndpoint
|
||||
AuthorizationEndpoint, IntrospectEndpoint, ResourceEndpoint,
|
||||
RevocationEndpoint, TokenEndpoint,
|
||||
)
|
||||
from oauthlib.oauth2.rfc6749.grant_types import (
|
||||
AuthorizationCodeGrant as OAuth2AuthorizationCodeGrant,
|
||||
ImplicitGrant as OAuth2ImplicitGrant,
|
||||
ClientCredentialsGrant,
|
||||
RefreshTokenGrant,
|
||||
ResourceOwnerPasswordCredentialsGrant
|
||||
ClientCredentialsGrant, ImplicitGrant as OAuth2ImplicitGrant,
|
||||
RefreshTokenGrant, ResourceOwnerPasswordCredentialsGrant,
|
||||
)
|
||||
from oauthlib.oauth2.rfc6749.tokens import BearerToken
|
||||
from ..grant_types import (
|
||||
AuthorizationCodeGrant,
|
||||
ImplicitGrant,
|
||||
HybridGrant,
|
||||
)
|
||||
|
||||
from ..grant_types import AuthorizationCodeGrant, HybridGrant, ImplicitGrant
|
||||
from ..grant_types.dispatchers import (
|
||||
AuthorizationCodeGrantDispatcher,
|
||||
AuthorizationCodeGrantDispatcher, AuthorizationTokenGrantDispatcher,
|
||||
ImplicitTokenGrantDispatcher,
|
||||
AuthorizationTokenGrantDispatcher
|
||||
)
|
||||
from ..tokens import JWTToken
|
||||
from .userinfo import UserInfoEndpoint
|
||||
|
@ -58,52 +46,52 @@ class Server(AuthorizationEndpoint, IntrospectEndpoint, TokenEndpoint,
|
|||
:param kwargs: Extra parameters to pass to authorization-,
|
||||
token-, resource-, and revocation-endpoint constructors.
|
||||
"""
|
||||
auth_grant = OAuth2AuthorizationCodeGrant(request_validator)
|
||||
implicit_grant = OAuth2ImplicitGrant(request_validator)
|
||||
password_grant = ResourceOwnerPasswordCredentialsGrant(
|
||||
self.auth_grant = OAuth2AuthorizationCodeGrant(request_validator)
|
||||
self.implicit_grant = OAuth2ImplicitGrant(request_validator)
|
||||
self.password_grant = ResourceOwnerPasswordCredentialsGrant(
|
||||
request_validator)
|
||||
credentials_grant = ClientCredentialsGrant(request_validator)
|
||||
refresh_grant = RefreshTokenGrant(request_validator)
|
||||
openid_connect_auth = AuthorizationCodeGrant(request_validator)
|
||||
openid_connect_implicit = ImplicitGrant(request_validator)
|
||||
openid_connect_hybrid = HybridGrant(request_validator)
|
||||
self.credentials_grant = ClientCredentialsGrant(request_validator)
|
||||
self.refresh_grant = RefreshTokenGrant(request_validator)
|
||||
self.openid_connect_auth = AuthorizationCodeGrant(request_validator)
|
||||
self.openid_connect_implicit = ImplicitGrant(request_validator)
|
||||
self.openid_connect_hybrid = HybridGrant(request_validator)
|
||||
|
||||
bearer = BearerToken(request_validator, token_generator,
|
||||
self.bearer = BearerToken(request_validator, token_generator,
|
||||
token_expires_in, refresh_token_generator)
|
||||
|
||||
jwt = JWTToken(request_validator, token_generator,
|
||||
self.jwt = JWTToken(request_validator, token_generator,
|
||||
token_expires_in, refresh_token_generator)
|
||||
|
||||
auth_grant_choice = AuthorizationCodeGrantDispatcher(default_grant=auth_grant, oidc_grant=openid_connect_auth)
|
||||
implicit_grant_choice = ImplicitTokenGrantDispatcher(default_grant=implicit_grant, oidc_grant=openid_connect_implicit)
|
||||
self.auth_grant_choice = AuthorizationCodeGrantDispatcher(default_grant=self.auth_grant, oidc_grant=self.openid_connect_auth)
|
||||
self.implicit_grant_choice = ImplicitTokenGrantDispatcher(default_grant=self.implicit_grant, oidc_grant=self.openid_connect_implicit)
|
||||
|
||||
# See http://openid.net/specs/oauth-v2-multiple-response-types-1_0.html#Combinations for valid combinations
|
||||
# internally our AuthorizationEndpoint will ensure they can appear in any order for any valid combination
|
||||
AuthorizationEndpoint.__init__(self, default_response_type='code',
|
||||
response_types={
|
||||
'code': auth_grant_choice,
|
||||
'token': implicit_grant_choice,
|
||||
'id_token': openid_connect_implicit,
|
||||
'id_token token': openid_connect_implicit,
|
||||
'code token': openid_connect_hybrid,
|
||||
'code id_token': openid_connect_hybrid,
|
||||
'code id_token token': openid_connect_hybrid,
|
||||
'none': auth_grant
|
||||
'code': self.auth_grant_choice,
|
||||
'token': self.implicit_grant_choice,
|
||||
'id_token': self.openid_connect_implicit,
|
||||
'id_token token': self.openid_connect_implicit,
|
||||
'code token': self.openid_connect_hybrid,
|
||||
'code id_token': self.openid_connect_hybrid,
|
||||
'code id_token token': self.openid_connect_hybrid,
|
||||
'none': self.auth_grant
|
||||
},
|
||||
default_token_type=bearer)
|
||||
default_token_type=self.bearer)
|
||||
|
||||
token_grant_choice = AuthorizationTokenGrantDispatcher(request_validator, default_grant=auth_grant, oidc_grant=openid_connect_auth)
|
||||
self.token_grant_choice = AuthorizationTokenGrantDispatcher(request_validator, default_grant=self.auth_grant, oidc_grant=self.openid_connect_auth)
|
||||
|
||||
TokenEndpoint.__init__(self, default_grant_type='authorization_code',
|
||||
grant_types={
|
||||
'authorization_code': token_grant_choice,
|
||||
'password': password_grant,
|
||||
'client_credentials': credentials_grant,
|
||||
'refresh_token': refresh_grant,
|
||||
'authorization_code': self.token_grant_choice,
|
||||
'password': self.password_grant,
|
||||
'client_credentials': self.credentials_grant,
|
||||
'refresh_token': self.refresh_grant,
|
||||
},
|
||||
default_token_type=bearer)
|
||||
default_token_type=self.bearer)
|
||||
ResourceEndpoint.__init__(self, default_token='Bearer',
|
||||
token_types={'Bearer': bearer, 'JWT': jwt})
|
||||
token_types={'Bearer': self.bearer, 'JWT': self.jwt})
|
||||
RevocationEndpoint.__init__(self, request_validator)
|
||||
IntrospectEndpoint.__init__(self, request_validator)
|
||||
UserInfoEndpoint.__init__(self, request_validator)
|
||||
|
|
|
@ -4,18 +4,15 @@ oauthlib.openid.connect.core.endpoints.userinfo
|
|||
|
||||
This module is an implementation of userinfo endpoint.
|
||||
"""
|
||||
from __future__ import absolute_import, unicode_literals
|
||||
|
||||
import json
|
||||
import logging
|
||||
|
||||
from oauthlib.common import Request
|
||||
from oauthlib.common import unicode_type
|
||||
from oauthlib.oauth2.rfc6749.endpoints.base import BaseEndpoint
|
||||
from oauthlib.oauth2.rfc6749.endpoints.base import catch_errors_and_unavailability
|
||||
from oauthlib.oauth2.rfc6749.tokens import BearerToken
|
||||
from oauthlib.oauth2.rfc6749 import errors
|
||||
|
||||
from oauthlib.oauth2.rfc6749.endpoints.base import (
|
||||
BaseEndpoint, catch_errors_and_unavailability,
|
||||
)
|
||||
from oauthlib.oauth2.rfc6749.tokens import BearerToken
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
@ -55,7 +52,7 @@ class UserInfoEndpoint(BaseEndpoint):
|
|||
log.error('Userinfo MUST have "sub" for %r.', request)
|
||||
raise errors.ServerError(status_code=500)
|
||||
body = json.dumps(claims)
|
||||
elif isinstance(claims, unicode_type):
|
||||
elif isinstance(claims, str):
|
||||
resp_headers = {
|
||||
'Content-Type': 'application/jwt'
|
||||
}
|
||||
|
@ -72,7 +69,7 @@ class UserInfoEndpoint(BaseEndpoint):
|
|||
5.3.1. UserInfo Request
|
||||
The Client sends the UserInfo Request using either HTTP GET or HTTP
|
||||
POST. The Access Token obtained from an OpenID Connect Authentication
|
||||
Request MUST be sent as a Bearer Token, per Section 2 of OAuth 2.0
|
||||
Request MUST be sent as a Bearer Token, per `Section 2`_ of OAuth 2.0
|
||||
Bearer Token Usage [RFC6750].
|
||||
|
||||
It is RECOMMENDED that the request use the HTTP GET method and the
|
||||
|
@ -80,21 +77,28 @@ class UserInfoEndpoint(BaseEndpoint):
|
|||
|
||||
The following is a non-normative example of a UserInfo Request:
|
||||
|
||||
GET /userinfo HTTP/1.1
|
||||
Host: server.example.com
|
||||
Authorization: Bearer SlAV32hkKG
|
||||
.. code-block:: http
|
||||
|
||||
GET /userinfo HTTP/1.1
|
||||
Host: server.example.com
|
||||
Authorization: Bearer SlAV32hkKG
|
||||
|
||||
5.3.3. UserInfo Error Response
|
||||
When an error condition occurs, the UserInfo Endpoint returns an Error
|
||||
Response as defined in Section 3 of OAuth 2.0 Bearer Token Usage
|
||||
Response as defined in `Section 3`_ of OAuth 2.0 Bearer Token Usage
|
||||
[RFC6750]. (HTTP errors unrelated to RFC 6750 are returned to the User
|
||||
Agent using the appropriate HTTP status code.)
|
||||
|
||||
The following is a non-normative example of a UserInfo Error Response:
|
||||
|
||||
HTTP/1.1 401 Unauthorized
|
||||
WWW-Authenticate: Bearer error="invalid_token",
|
||||
.. code-block:: http
|
||||
|
||||
HTTP/1.1 401 Unauthorized
|
||||
WWW-Authenticate: Bearer error="invalid_token",
|
||||
error_description="The Access Token expired"
|
||||
|
||||
.. _`Section 2`: https://datatracker.ietf.org/doc/html/rfc6750#section-2
|
||||
.. _`Section 3`: https://datatracker.ietf.org/doc/html/rfc6750#section-3
|
||||
"""
|
||||
if not self.bearer.validate_request(request):
|
||||
raise errors.InvalidTokenError()
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
# coding=utf-8
|
||||
"""
|
||||
oauthlib.oauth2.rfc6749.errors
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
@ -6,8 +5,6 @@ oauthlib.oauth2.rfc6749.errors
|
|||
Error used both by OAuth 2 clients and providers to represent the spec
|
||||
defined error responses for all four core grant types.
|
||||
"""
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from oauthlib.oauth2.rfc6749.errors import FatalClientError, OAuth2Error
|
||||
|
||||
|
||||
|
|
|
@ -1,17 +1,13 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
oauthlib.openid.connect.core.grant_types
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
"""
|
||||
from __future__ import unicode_literals, absolute_import
|
||||
|
||||
from .authorization_code import AuthorizationCodeGrant
|
||||
from .implicit import ImplicitGrant
|
||||
from .base import GrantTypeBase
|
||||
from .hybrid import HybridGrant
|
||||
from .exceptions import OIDCNoPrompt
|
||||
from .dispatchers import (
|
||||
AuthorizationCodeGrantDispatcher,
|
||||
AuthorizationCodeGrantDispatcher, AuthorizationTokenGrantDispatcher,
|
||||
ImplicitTokenGrantDispatcher,
|
||||
AuthorizationTokenGrantDispatcher
|
||||
)
|
||||
from .hybrid import HybridGrant
|
||||
from .implicit import ImplicitGrant
|
||||
from .refresh_token import RefreshTokenGrant
|
||||
|
|
|
@ -1,13 +1,12 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
oauthlib.openid.connect.core.grant_types
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
"""
|
||||
from __future__ import absolute_import, unicode_literals
|
||||
|
||||
import logging
|
||||
|
||||
from oauthlib.oauth2.rfc6749.grant_types.authorization_code import AuthorizationCodeGrant as OAuth2AuthorizationCodeGrant
|
||||
from oauthlib.oauth2.rfc6749.grant_types.authorization_code import (
|
||||
AuthorizationCodeGrant as OAuth2AuthorizationCodeGrant,
|
||||
)
|
||||
|
||||
from .base import GrantTypeBase
|
||||
|
||||
|
@ -41,4 +40,4 @@ class AuthorizationCodeGrant(GrantTypeBase):
|
|||
request.redirect_uri,
|
||||
request
|
||||
)
|
||||
return super(AuthorizationCodeGrant, self).add_id_token(token, token_handler, request, nonce=nonce)
|
||||
return super().add_id_token(token, token_handler, request, nonce=nonce)
|
||||
|
|
|
@ -1,17 +1,17 @@
|
|||
from .exceptions import OIDCNoPrompt
|
||||
|
||||
import base64
|
||||
import hashlib
|
||||
import logging
|
||||
import time
|
||||
from json import loads
|
||||
|
||||
from oauthlib.oauth2.rfc6749.errors import ConsentRequired, InvalidRequestError, LoginRequired
|
||||
from oauthlib.oauth2.rfc6749.errors import (
|
||||
ConsentRequired, InvalidRequestError, LoginRequired,
|
||||
)
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class GrantTypeBase(object):
|
||||
class GrantTypeBase:
|
||||
|
||||
# Just proxy the majority of method calls through to the
|
||||
# proxy_target grant type handler, which will usually be either
|
||||
|
@ -20,7 +20,7 @@ class GrantTypeBase(object):
|
|||
return getattr(self.proxy_target, attr)
|
||||
|
||||
def __setattr__(self, attr, value):
|
||||
proxied_attrs = set(('refresh_token', 'response_types'))
|
||||
proxied_attrs = {'refresh_token', 'response_types'}
|
||||
if attr in proxied_attrs:
|
||||
setattr(self.proxy_target, attr, value)
|
||||
else:
|
||||
|
@ -31,13 +31,7 @@ class GrantTypeBase(object):
|
|||
|
||||
:returns: (list of scopes, dict of request info)
|
||||
"""
|
||||
# If request.prompt is 'none' then no login/authorization form should
|
||||
# be presented to the user. Instead, a silent login/authorization
|
||||
# should be performed.
|
||||
if request.prompt == 'none':
|
||||
raise OIDCNoPrompt()
|
||||
else:
|
||||
return self.proxy_target.validate_authorization_request(request)
|
||||
return self.proxy_target.validate_authorization_request(request)
|
||||
|
||||
def _inflate_claims(self, request):
|
||||
# this may be called multiple times in a single request so make sure we only de-serialize the claims once
|
||||
|
|
|
@ -1,16 +1,19 @@
|
|||
import logging
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Dispatcher(object):
|
||||
class Dispatcher:
|
||||
default_grant = None
|
||||
oidc_grant = None
|
||||
|
||||
|
||||
class AuthorizationCodeGrantDispatcher(Dispatcher):
|
||||
"""
|
||||
This is an adapter class that will route simple Authorization Code requests, those that have response_type=code and a scope
|
||||
including 'openid' to either the default_grant or the oidc_grant based on the scopes requested.
|
||||
This is an adapter class that will route simple Authorization Code
|
||||
requests, those that have `response_type=code` and a scope including
|
||||
`openid` to either the `default_grant` or the `oidc_grant` based on
|
||||
the scopes requested.
|
||||
"""
|
||||
def __init__(self, default_grant=None, oidc_grant=None):
|
||||
self.default_grant = default_grant
|
||||
|
@ -26,16 +29,20 @@ class AuthorizationCodeGrantDispatcher(Dispatcher):
|
|||
return handler
|
||||
|
||||
def create_authorization_response(self, request, token_handler):
|
||||
"""Read scope and route to the designated handler."""
|
||||
return self._handler_for_request(request).create_authorization_response(request, token_handler)
|
||||
|
||||
def validate_authorization_request(self, request):
|
||||
"""Read scope and route to the designated handler."""
|
||||
return self._handler_for_request(request).validate_authorization_request(request)
|
||||
|
||||
|
||||
class ImplicitTokenGrantDispatcher(Dispatcher):
|
||||
"""
|
||||
This is an adapter class that will route simple Authorization Code requests, those that have response_type=code and a scope
|
||||
including 'openid' to either the default_grant or the oidc_grant based on the scopes requested.
|
||||
This is an adapter class that will route simple Authorization
|
||||
requests, those that have `id_token` in `response_type` and a scope
|
||||
including `openid` to either the `default_grant` or the `oidc_grant`
|
||||
based on the scopes requested.
|
||||
"""
|
||||
def __init__(self, default_grant=None, oidc_grant=None):
|
||||
self.default_grant = default_grant
|
||||
|
@ -51,9 +58,11 @@ class ImplicitTokenGrantDispatcher(Dispatcher):
|
|||
return handler
|
||||
|
||||
def create_authorization_response(self, request, token_handler):
|
||||
"""Read scope and route to the designated handler."""
|
||||
return self._handler_for_request(request).create_authorization_response(request, token_handler)
|
||||
|
||||
def validate_authorization_request(self, request):
|
||||
"""Read scope and route to the designated handler."""
|
||||
return self._handler_for_request(request).validate_authorization_request(request)
|
||||
|
||||
|
||||
|
@ -75,7 +84,7 @@ class AuthorizationTokenGrantDispatcher(Dispatcher):
|
|||
code = parameters.get('code', None)
|
||||
redirect_uri = parameters.get('redirect_uri', None)
|
||||
|
||||
# If code is not pressent fallback to `default_grant` wich will
|
||||
# If code is not present fallback to `default_grant` which will
|
||||
# raise an error for the missing `code` in `create_token_response` step.
|
||||
if code:
|
||||
scopes = self.request_validator.get_authorization_code_scopes(client_id, code, redirect_uri, request)
|
||||
|
@ -87,5 +96,6 @@ class AuthorizationTokenGrantDispatcher(Dispatcher):
|
|||
return handler
|
||||
|
||||
def create_token_response(self, request, token_handler):
|
||||
"""Read scope and route to the designated handler."""
|
||||
handler = self._handler_for_request(request)
|
||||
return handler.create_token_response(request, token_handler)
|
||||
|
|
|
@ -1,32 +0,0 @@
|
|||
class OIDCNoPrompt(Exception):
|
||||
"""Exception used to inform users that no explicit authorization is needed.
|
||||
|
||||
Normally users authorize requests after validation of the request is done.
|
||||
Then post-authorization validation is again made and a response containing
|
||||
an auth code or token is created. However, when OIDC clients request
|
||||
no prompting of user authorization the final response is created directly.
|
||||
|
||||
Example (without the shortcut for no prompt)
|
||||
|
||||
scopes, req_info = endpoint.validate_authorization_request(url, ...)
|
||||
authorization_view = create_fancy_auth_form(scopes, req_info)
|
||||
return authorization_view
|
||||
|
||||
Example (with the no prompt shortcut)
|
||||
try:
|
||||
scopes, req_info = endpoint.validate_authorization_request(url, ...)
|
||||
authorization_view = create_fancy_auth_form(scopes, req_info)
|
||||
return authorization_view
|
||||
except OIDCNoPrompt:
|
||||
# Note: Location will be set for you
|
||||
headers, body, status = endpoint.create_authorization_response(url, ...)
|
||||
redirect_view = create_redirect(headers, body, status)
|
||||
return redirect_view
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
msg = ("OIDC request for no user interaction received. Do not ask user "
|
||||
"for authorization, it should been done using silent "
|
||||
"authentication through create_authorization_response. "
|
||||
"See OIDCNoPrompt.__doc__ for more details.")
|
||||
super(OIDCNoPrompt, self).__init__(msg)
|
|
@ -1,17 +1,16 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
oauthlib.openid.connect.core.grant_types
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
"""
|
||||
from __future__ import absolute_import, unicode_literals
|
||||
|
||||
import logging
|
||||
|
||||
from oauthlib.oauth2.rfc6749.grant_types.authorization_code import AuthorizationCodeGrant as OAuth2AuthorizationCodeGrant
|
||||
from oauthlib.oauth2.rfc6749.errors import InvalidRequestError
|
||||
from oauthlib.oauth2.rfc6749.grant_types.authorization_code import (
|
||||
AuthorizationCodeGrant as OAuth2AuthorizationCodeGrant,
|
||||
)
|
||||
|
||||
from .base import GrantTypeBase
|
||||
from ..request_validator import RequestValidator
|
||||
from .base import GrantTypeBase
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
@ -36,10 +35,13 @@ class HybridGrant(GrantTypeBase):
|
|||
self.register_code_modifier(self.add_id_token)
|
||||
self.register_token_modifier(self.add_id_token)
|
||||
|
||||
def add_id_token(self, token, token_handler, request):
|
||||
return super().add_id_token(token, token_handler, request, nonce=request.nonce)
|
||||
|
||||
def openid_authorization_validator(self, request):
|
||||
"""Additional validation when following the Authorization Code flow.
|
||||
"""
|
||||
request_info = super(HybridGrant, self).openid_authorization_validator(request)
|
||||
request_info = super().openid_authorization_validator(request)
|
||||
if not request_info: # returns immediately if OAuth2.0
|
||||
return request_info
|
||||
|
||||
|
|
|
@ -1,16 +1,15 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
oauthlib.openid.connect.core.grant_types
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
"""
|
||||
from __future__ import absolute_import, unicode_literals
|
||||
|
||||
import logging
|
||||
|
||||
from .base import GrantTypeBase
|
||||
|
||||
from oauthlib.oauth2.rfc6749.grant_types.implicit import ImplicitGrant as OAuth2ImplicitGrant
|
||||
from oauthlib.oauth2.rfc6749.errors import InvalidRequestError
|
||||
from oauthlib.oauth2.rfc6749.grant_types.implicit import (
|
||||
ImplicitGrant as OAuth2ImplicitGrant,
|
||||
)
|
||||
|
||||
from .base import GrantTypeBase
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
@ -29,12 +28,12 @@ class ImplicitGrant(GrantTypeBase):
|
|||
def add_id_token(self, token, token_handler, request):
|
||||
if 'state' not in token and request.state:
|
||||
token['state'] = request.state
|
||||
return super(ImplicitGrant, self).add_id_token(token, token_handler, request, nonce=request.nonce)
|
||||
return super().add_id_token(token, token_handler, request, nonce=request.nonce)
|
||||
|
||||
def openid_authorization_validator(self, request):
|
||||
"""Additional validation when following the implicit flow.
|
||||
"""
|
||||
request_info = super(ImplicitGrant, self).openid_authorization_validator(request)
|
||||
request_info = super().openid_authorization_validator(request)
|
||||
if not request_info: # returns immediately if OAuth2.0
|
||||
return request_info
|
||||
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
"""
|
||||
oauthlib.openid.connect.core.grant_types
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
"""
|
||||
import logging
|
||||
|
||||
from oauthlib.oauth2.rfc6749.grant_types.refresh_token import (
|
||||
RefreshTokenGrant as OAuth2RefreshTokenGrant,
|
||||
)
|
||||
|
||||
from .base import GrantTypeBase
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class RefreshTokenGrant(GrantTypeBase):
|
||||
|
||||
def __init__(self, request_validator=None, **kwargs):
|
||||
self.proxy_target = OAuth2RefreshTokenGrant(
|
||||
request_validator=request_validator, **kwargs)
|
||||
self.register_token_modifier(self.add_id_token)
|
||||
|
||||
def add_id_token(self, token, token_handler, request):
|
||||
"""
|
||||
Construct an initial version of id_token, and let the
|
||||
request_validator sign or encrypt it.
|
||||
|
||||
The authorization_code version of this method is used to
|
||||
retrieve the nonce accordingly to the code storage.
|
||||
"""
|
||||
if not self.request_validator.refresh_id_token(request):
|
||||
return token
|
||||
|
||||
return super().add_id_token(token, token_handler, request)
|
|
@ -1,13 +1,12 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
oauthlib.openid.connect.core.request_validator
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
"""
|
||||
from __future__ import absolute_import, unicode_literals
|
||||
|
||||
import logging
|
||||
|
||||
from oauthlib.oauth2.rfc6749.request_validator import RequestValidator as OAuth2RequestValidator
|
||||
from oauthlib.oauth2.rfc6749.request_validator import (
|
||||
RequestValidator as OAuth2RequestValidator,
|
||||
)
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
@ -307,3 +306,15 @@ class RequestValidator(OAuth2RequestValidator):
|
|||
Method is used by:
|
||||
UserInfoEndpoint
|
||||
"""
|
||||
|
||||
def refresh_id_token(self, request):
|
||||
"""Whether the id token should be refreshed. Default, True
|
||||
|
||||
:param request: OAuthlib request.
|
||||
:type request: oauthlib.common.Request
|
||||
:rtype: True or False
|
||||
|
||||
Method is used by:
|
||||
RefreshTokenGrant
|
||||
"""
|
||||
return True
|
||||
|
|
|
@ -4,10 +4,9 @@ authlib.openid.connect.core.tokens
|
|||
|
||||
This module contains methods for adding JWT tokens to requests.
|
||||
"""
|
||||
from __future__ import absolute_import, unicode_literals
|
||||
|
||||
|
||||
from oauthlib.oauth2.rfc6749.tokens import TokenBase, random_token_generator
|
||||
from oauthlib.oauth2.rfc6749.tokens import (
|
||||
TokenBase, get_token_from_header, random_token_generator,
|
||||
)
|
||||
|
||||
|
||||
class JWTToken(TokenBase):
|
||||
|
@ -38,17 +37,12 @@ class JWTToken(TokenBase):
|
|||
return self.request_validator.get_jwt_bearer_token(None, None, request)
|
||||
|
||||
def validate_request(self, request):
|
||||
token = None
|
||||
if 'Authorization' in request.headers:
|
||||
token = request.headers.get('Authorization')[7:]
|
||||
else:
|
||||
token = request.access_token
|
||||
token = get_token_from_header(request)
|
||||
return self.request_validator.validate_jwt_bearer_token(
|
||||
token, request.scopes, request)
|
||||
|
||||
def estimate_type(self, request):
|
||||
token = request.headers.get('Authorization', '')[7:]
|
||||
if token.startswith('ey') and token.count('.') in (2, 4):
|
||||
token = get_token_from_header(request)
|
||||
if token and token.startswith('ey') and token.count('.') in (2, 4):
|
||||
return 10
|
||||
else:
|
||||
return 0
|
||||
return 0
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Implements signals based on blinker if available, otherwise
|
||||
falls silently back to a noop. Shamelessly stolen from flask.signals:
|
||||
|
@ -9,11 +8,11 @@ try:
|
|||
from blinker import Namespace
|
||||
signals_available = True
|
||||
except ImportError: # noqa
|
||||
class Namespace(object):
|
||||
class Namespace:
|
||||
def signal(self, name, doc=None):
|
||||
return _FakeSignal(name, doc)
|
||||
|
||||
class _FakeSignal(object):
|
||||
class _FakeSignal:
|
||||
"""If blinker is unavailable, create a fake class with the same
|
||||
interface that allows sending of signals but will fail with an
|
||||
error on anything else. Instead of doing anything on send, it
|
||||
|
|
|
@ -8,8 +8,6 @@ They should be processed with re.VERBOSE.
|
|||
|
||||
Thanks Mark Nottingham for this code - https://gist.github.com/138549
|
||||
"""
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import re
|
||||
|
||||
# basics
|
||||
|
@ -67,32 +65,8 @@ dec_octet = r"""(?: %(DIGIT)s |
|
|||
IPv4address = r"%(dec_octet)s \. %(dec_octet)s \. %(dec_octet)s \. %(dec_octet)s" % locals(
|
||||
)
|
||||
|
||||
# h16 = 1*4HEXDIG
|
||||
h16 = r"(?: %(HEXDIG)s ){1,4}" % locals()
|
||||
|
||||
# ls32 = ( h16 ":" h16 ) / IPv4address
|
||||
ls32 = r"(?: (?: %(h16)s : %(h16)s ) | %(IPv4address)s )" % locals()
|
||||
|
||||
# IPv6address = 6( h16 ":" ) ls32
|
||||
# / "::" 5( h16 ":" ) ls32
|
||||
# / [ h16 ] "::" 4( h16 ":" ) ls32
|
||||
# / [ *1( h16 ":" ) h16 ] "::" 3( h16 ":" ) ls32
|
||||
# / [ *2( h16 ":" ) h16 ] "::" 2( h16 ":" ) ls32
|
||||
# / [ *3( h16 ":" ) h16 ] "::" h16 ":" ls32
|
||||
# / [ *4( h16 ":" ) h16 ] "::" ls32
|
||||
# / [ *5( h16 ":" ) h16 ] "::" h16
|
||||
# / [ *6( h16 ":" ) h16 ] "::"
|
||||
IPv6address = r"""(?: (?: %(h16)s : ){6} %(ls32)s |
|
||||
:: (?: %(h16)s : ){5} %(ls32)s |
|
||||
%(h16)s :: (?: %(h16)s : ){4} %(ls32)s |
|
||||
(?: %(h16)s : ) %(h16)s :: (?: %(h16)s : ){3} %(ls32)s |
|
||||
(?: %(h16)s : ){2} %(h16)s :: (?: %(h16)s : ){2} %(ls32)s |
|
||||
(?: %(h16)s : ){3} %(h16)s :: %(h16)s : %(ls32)s |
|
||||
(?: %(h16)s : ){4} %(h16)s :: %(ls32)s |
|
||||
(?: %(h16)s : ){5} %(h16)s :: %(h16)s |
|
||||
(?: %(h16)s : ){6} %(h16)s ::
|
||||
)
|
||||
""" % locals()
|
||||
# IPv6address
|
||||
IPv6address = r"([A-Fa-f0-9:]+[:$])[A-Fa-f0-9]{1,4}"
|
||||
|
||||
# IPvFuture = "v" 1*HEXDIG "." 1*( unreserved / sub-delims / ":" )
|
||||
IPvFuture = r"v %(HEXDIG)s+ \. (?: %(unreserved)s | %(sub_delims)s | : )+" % locals()
|
||||
|
|
13
setup.cfg
13
setup.cfg
|
@ -1,9 +1,16 @@
|
|||
[bdist_wheel]
|
||||
universal = 1
|
||||
|
||||
[metadata]
|
||||
license_file = LICENSE
|
||||
|
||||
[isort]
|
||||
combine_as_imports = true
|
||||
default_section = THIRDPARTY
|
||||
include_trailing_comma = true
|
||||
known_first_party = oauthlib
|
||||
known_tests = tests
|
||||
sections = FUTURE,STDLIB,THIRDPARTY,FIRSTPARTY,TESTS,LOCALFOLDER
|
||||
line_length = 79
|
||||
multi_line_output = 5
|
||||
|
||||
[egg_info]
|
||||
tag_build =
|
||||
tag_date = 0
|
||||
|
|
20
setup.py
20
setup.py
|
@ -1,12 +1,9 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Hack because logging + setuptools sucks.
|
||||
try:
|
||||
import multiprocessing
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
import sys
|
||||
from os.path import dirname, join
|
||||
|
||||
from setuptools import find_packages, setup
|
||||
|
@ -19,15 +16,16 @@ def fread(fn):
|
|||
return f.read()
|
||||
|
||||
|
||||
rsa_require = ['cryptography']
|
||||
signedtoken_require = ['cryptography', 'pyjwt>=1.0.0']
|
||||
signals_require = ['blinker']
|
||||
rsa_require = ['cryptography>=3.0.0']
|
||||
signedtoken_require = ['cryptography>=3.0.0', 'pyjwt>=2.0.0,<3']
|
||||
signals_require = ['blinker>=1.4.0']
|
||||
|
||||
setup(
|
||||
name='oauthlib',
|
||||
version=oauthlib.__version__,
|
||||
description='A generic, spec-compliant, thorough implementation of the OAuth request-signing logic',
|
||||
long_description=fread('README.rst'),
|
||||
long_description_content_type='text/x-rst',
|
||||
author='The OAuthlib Community',
|
||||
author_email='idan@gazit.me',
|
||||
maintainer='Ib Lundgren',
|
||||
|
@ -36,7 +34,7 @@ setup(
|
|||
platforms='any',
|
||||
license='BSD',
|
||||
packages=find_packages(exclude=('docs', 'tests', 'tests.*')),
|
||||
python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*',
|
||||
python_requires='>=3.6',
|
||||
extras_require={
|
||||
'rsa': rsa_require,
|
||||
'signedtoken': signedtoken_require,
|
||||
|
@ -52,13 +50,13 @@ setup(
|
|||
'Operating System :: POSIX',
|
||||
'Operating System :: POSIX :: Linux',
|
||||
'Programming Language :: Python',
|
||||
'Programming Language :: Python :: 2',
|
||||
'Programming Language :: Python :: 2.7',
|
||||
'Programming Language :: Python :: 3',
|
||||
'Programming Language :: Python :: 3.4',
|
||||
'Programming Language :: Python :: 3.5',
|
||||
'Programming Language :: Python :: 3.6',
|
||||
'Programming Language :: Python :: 3.7',
|
||||
'Programming Language :: Python :: 3.8',
|
||||
'Programming Language :: Python :: 3.9',
|
||||
'Programming Language :: Python :: 3.10',
|
||||
'Programming Language :: Python :: 3 :: Only',
|
||||
'Programming Language :: Python :: Implementation',
|
||||
'Programming Language :: Python :: Implementation :: CPython',
|
||||
'Programming Language :: Python :: Implementation :: PyPy',
|
||||
|
|
|
@ -1,12 +1,10 @@
|
|||
from __future__ import absolute_import, unicode_literals
|
||||
|
||||
from mock import ANY, MagicMock
|
||||
from unittest.mock import ANY, MagicMock
|
||||
|
||||
from oauthlib.oauth1 import RequestValidator
|
||||
from oauthlib.oauth1.rfc5849 import Client
|
||||
from oauthlib.oauth1.rfc5849.endpoints import AccessTokenEndpoint
|
||||
|
||||
from ....unittest import TestCase
|
||||
from tests.unittest import TestCase
|
||||
|
||||
|
||||
class AccessTokenEndpointTest(TestCase):
|
||||
|
|
|
@ -1,6 +1,4 @@
|
|||
from __future__ import absolute_import, unicode_literals
|
||||
|
||||
from mock import MagicMock
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
from oauthlib.oauth1 import RequestValidator
|
||||
from oauthlib.oauth1.rfc5849 import errors
|
||||
|
|
|
@ -1,17 +1,16 @@
|
|||
from __future__ import absolute_import, unicode_literals
|
||||
|
||||
from re import sub
|
||||
|
||||
from mock import MagicMock
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
from oauthlib.common import CaseInsensitiveDict, safe_string_equals
|
||||
from oauthlib.oauth1 import Client, RequestValidator
|
||||
from oauthlib.oauth1.rfc5849 import (SIGNATURE_HMAC, SIGNATURE_PLAINTEXT,
|
||||
SIGNATURE_RSA, errors)
|
||||
from oauthlib.oauth1.rfc5849.endpoints import (BaseEndpoint,
|
||||
RequestTokenEndpoint)
|
||||
from oauthlib.oauth1.rfc5849 import (
|
||||
SIGNATURE_HMAC, SIGNATURE_PLAINTEXT, SIGNATURE_RSA, errors,
|
||||
)
|
||||
from oauthlib.oauth1.rfc5849.endpoints import (
|
||||
BaseEndpoint, RequestTokenEndpoint,
|
||||
)
|
||||
|
||||
from ....unittest import TestCase
|
||||
from tests.unittest import TestCase
|
||||
|
||||
URLENCODED = {"Content-Type": "application/x-www-form-urlencoded"}
|
||||
|
||||
|
|
|
@ -1,12 +1,10 @@
|
|||
from __future__ import absolute_import, unicode_literals
|
||||
|
||||
from mock import ANY, MagicMock
|
||||
from unittest.mock import ANY, MagicMock
|
||||
|
||||
from oauthlib.oauth1 import RequestValidator
|
||||
from oauthlib.oauth1.rfc5849 import Client
|
||||
from oauthlib.oauth1.rfc5849.endpoints import RequestTokenEndpoint
|
||||
|
||||
from ....unittest import TestCase
|
||||
from tests.unittest import TestCase
|
||||
|
||||
|
||||
class RequestTokenEndpointTest(TestCase):
|
||||
|
|
|
@ -1,12 +1,10 @@
|
|||
from __future__ import absolute_import, unicode_literals
|
||||
|
||||
from mock import ANY, MagicMock
|
||||
from unittest.mock import ANY, MagicMock
|
||||
|
||||
from oauthlib.oauth1 import RequestValidator
|
||||
from oauthlib.oauth1.rfc5849 import Client
|
||||
from oauthlib.oauth1.rfc5849.endpoints import ResourceEndpoint
|
||||
|
||||
from ....unittest import TestCase
|
||||
from tests.unittest import TestCase
|
||||
|
||||
|
||||
class ResourceEndpointTest(TestCase):
|
||||
|
|
|
@ -1,12 +1,10 @@
|
|||
from __future__ import absolute_import, unicode_literals
|
||||
|
||||
from mock import ANY, MagicMock
|
||||
from unittest.mock import ANY, MagicMock
|
||||
|
||||
from oauthlib.oauth1 import RequestValidator
|
||||
from oauthlib.oauth1.rfc5849 import Client
|
||||
from oauthlib.oauth1.rfc5849.endpoints import SignatureOnlyEndpoint
|
||||
|
||||
from ....unittest import TestCase
|
||||
from tests.unittest import TestCase
|
||||
|
||||
|
||||
class SignatureOnlyEndpointTest(TestCase):
|
||||
|
|
|
@ -1,13 +1,12 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import absolute_import, unicode_literals
|
||||
|
||||
from oauthlib.common import Request
|
||||
from oauthlib.oauth1 import (SIGNATURE_PLAINTEXT, SIGNATURE_HMAC_SHA1,
|
||||
SIGNATURE_HMAC_SHA256, SIGNATURE_RSA,
|
||||
SIGNATURE_TYPE_BODY, SIGNATURE_TYPE_QUERY)
|
||||
from oauthlib.oauth1 import (
|
||||
SIGNATURE_HMAC_SHA1, SIGNATURE_HMAC_SHA256, SIGNATURE_PLAINTEXT,
|
||||
SIGNATURE_RSA, SIGNATURE_TYPE_BODY, SIGNATURE_TYPE_QUERY,
|
||||
)
|
||||
from oauthlib.oauth1.rfc5849 import Client
|
||||
|
||||
from ...unittest import TestCase
|
||||
from tests.unittest import TestCase
|
||||
|
||||
|
||||
class ClientRealmTests(TestCase):
|
||||
|
@ -163,7 +162,7 @@ class SignatureMethodTest(TestCase):
|
|||
Client.register_signature_method('PIZZA',
|
||||
lambda base_string, client: 'PIZZA')
|
||||
|
||||
self.assertTrue('PIZZA' in Client.SIGNATURE_METHODS)
|
||||
self.assertIn('PIZZA', Client.SIGNATURE_METHODS)
|
||||
|
||||
client = Client('client_key', signature_method='PIZZA',
|
||||
timestamp='1234567890', nonce='abc')
|
||||
|
|
|
@ -1,13 +1,11 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import absolute_import, unicode_literals
|
||||
|
||||
from oauthlib.common import urlencode
|
||||
from oauthlib.oauth1.rfc5849.parameters import (_append_params,
|
||||
prepare_form_encoded_body,
|
||||
prepare_headers,
|
||||
prepare_request_uri_query)
|
||||
from oauthlib.oauth1.rfc5849.parameters import (
|
||||
_append_params, prepare_form_encoded_body, prepare_headers,
|
||||
prepare_request_uri_query,
|
||||
)
|
||||
|
||||
from ...unittest import TestCase
|
||||
from tests.unittest import TestCase
|
||||
|
||||
|
||||
class ParameterTests(TestCase):
|
||||
|
|
|
@ -1,9 +1,7 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import absolute_import, unicode_literals
|
||||
|
||||
from oauthlib.oauth1 import RequestValidator
|
||||
|
||||
from ...unittest import TestCase
|
||||
from tests.unittest import TestCase
|
||||
|
||||
|
||||
class RequestValidatorTests(TestCase):
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,10 +1,7 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import absolute_import, unicode_literals
|
||||
|
||||
from oauthlib.common import unicode_type
|
||||
from oauthlib.oauth1.rfc5849.utils import *
|
||||
|
||||
from ...unittest import TestCase
|
||||
from tests.unittest import TestCase
|
||||
|
||||
|
||||
class UtilsTests(TestCase):
|
||||
|
@ -102,12 +99,12 @@ class UtilsTests(TestCase):
|
|||
def test_escape(self):
|
||||
self.assertRaises(ValueError, escape, b"I am a string type. Not a unicode type.")
|
||||
self.assertEqual(escape("I am a unicode type."), "I%20am%20a%20unicode%20type.")
|
||||
self.assertIsInstance(escape("I am a unicode type."), unicode_type)
|
||||
self.assertIsInstance(escape("I am a unicode type."), str)
|
||||
|
||||
def test_unescape(self):
|
||||
self.assertRaises(ValueError, unescape, b"I am a string type. Not a unicode type.")
|
||||
self.assertEqual(unescape("I%20am%20a%20unicode%20type."), 'I am a unicode type.')
|
||||
self.assertIsInstance(unescape("I%20am%20a%20unicode%20type."), unicode_type)
|
||||
self.assertIsInstance(unescape("I%20am%20a%20unicode%20type."), str)
|
||||
|
||||
def test_parse_authorization_header(self):
|
||||
# make us some headers
|
||||
|
@ -122,8 +119,8 @@ class UtilsTests(TestCase):
|
|||
|
||||
# are the internal components of each tuple unicode?
|
||||
for k, v in authorization_headers:
|
||||
self.assertIsInstance(k, unicode_type)
|
||||
self.assertIsInstance(v, unicode_type)
|
||||
self.assertIsInstance(k, str)
|
||||
self.assertIsInstance(v, str)
|
||||
|
||||
# let's check the parsed headers created
|
||||
correct_headers = [
|
||||
|
|
|
@ -1,14 +1,11 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import absolute_import, unicode_literals
|
||||
|
||||
import os
|
||||
|
||||
from mock import patch
|
||||
from unittest.mock import patch
|
||||
|
||||
from oauthlib import signals
|
||||
from oauthlib.oauth2 import BackendApplicationClient
|
||||
|
||||
from ....unittest import TestCase
|
||||
from tests.unittest import TestCase
|
||||
|
||||
|
||||
@patch('time.time', new=lambda: 1000)
|
||||
|
|
|
@ -1,6 +1,4 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import absolute_import, unicode_literals
|
||||
|
||||
import datetime
|
||||
|
||||
from oauthlib import common
|
||||
|
@ -8,7 +6,7 @@ from oauthlib.oauth2 import Client, InsecureTransportError, TokenExpiredError
|
|||
from oauthlib.oauth2.rfc6749 import utils
|
||||
from oauthlib.oauth2.rfc6749.clients import AUTH_HEADER, BODY, URI_QUERY
|
||||
|
||||
from ....unittest import TestCase
|
||||
from tests.unittest import TestCase
|
||||
|
||||
|
||||
class ClientTest(TestCase):
|
||||
|
@ -295,11 +293,63 @@ class ClientTest(TestCase):
|
|||
u, h, b = client.prepare_refresh_token_request(url, token, scope=scope)
|
||||
self.assertEqual(u, url)
|
||||
self.assertEqual(h, {'Content-Type': 'application/x-www-form-urlencoded'})
|
||||
self.assertFormBodyEqual(b, 'grant_type=refresh_token&scope=%s&refresh_token=%s' % (scope, token))
|
||||
self.assertFormBodyEqual(b, 'grant_type=refresh_token&scope={}&refresh_token={}'.format(scope, token))
|
||||
|
||||
# provide scope while init
|
||||
client = Client(self.client_id, scope=scope)
|
||||
u, h, b = client.prepare_refresh_token_request(url, token, scope=scope)
|
||||
self.assertEqual(u, url)
|
||||
self.assertEqual(h, {'Content-Type': 'application/x-www-form-urlencoded'})
|
||||
self.assertFormBodyEqual(b, 'grant_type=refresh_token&scope=%s&refresh_token=%s' % (scope, token))
|
||||
self.assertFormBodyEqual(b, 'grant_type=refresh_token&scope={}&refresh_token={}'.format(scope, token))
|
||||
|
||||
def test_parse_token_response_invalid_expires_at(self):
|
||||
token_json = ('{ "access_token":"2YotnFZFEjr1zCsicMWpAA",'
|
||||
' "token_type":"example",'
|
||||
' "expires_at":"2006-01-02T15:04:05Z",'
|
||||
' "scope":"/profile",'
|
||||
' "example_parameter":"example_value"}')
|
||||
token = {
|
||||
"access_token": "2YotnFZFEjr1zCsicMWpAA",
|
||||
"token_type": "example",
|
||||
"expires_at": "2006-01-02T15:04:05Z",
|
||||
"scope": ["/profile"],
|
||||
"example_parameter": "example_value"
|
||||
}
|
||||
|
||||
client = Client(self.client_id)
|
||||
|
||||
# Parse code and state
|
||||
response = client.parse_request_body_response(token_json, scope=["/profile"])
|
||||
self.assertEqual(response, token)
|
||||
self.assertEqual(None, client._expires_at)
|
||||
self.assertEqual(client.access_token, response.get("access_token"))
|
||||
self.assertEqual(client.refresh_token, response.get("refresh_token"))
|
||||
self.assertEqual(client.token_type, response.get("token_type"))
|
||||
|
||||
|
||||
def test_create_code_verifier_min_length(self):
|
||||
client = Client(self.client_id)
|
||||
length = 43
|
||||
code_verifier = client.create_code_verifier(length=length)
|
||||
self.assertEqual(client.code_verifier, code_verifier)
|
||||
|
||||
def test_create_code_verifier_max_length(self):
|
||||
client = Client(self.client_id)
|
||||
length = 128
|
||||
code_verifier = client.create_code_verifier(length=length)
|
||||
self.assertEqual(client.code_verifier, code_verifier)
|
||||
|
||||
def test_create_code_challenge_plain(self):
|
||||
client = Client(self.client_id)
|
||||
code_verifier = client.create_code_verifier(length=128)
|
||||
code_challenge_plain = client.create_code_challenge(code_verifier=code_verifier)
|
||||
|
||||
# if no code_challenge_method specified, code_challenge = code_verifier
|
||||
self.assertEqual(code_challenge_plain, client.code_verifier)
|
||||
self.assertEqual(client.code_challenge_method, "plain")
|
||||
|
||||
def test_create_code_challenge_s256(self):
|
||||
client = Client(self.client_id)
|
||||
code_verifier = client.create_code_verifier(length=128)
|
||||
code_challenge_s256 = client.create_code_challenge(code_verifier=code_verifier, code_challenge_method='S256')
|
||||
self.assertEqual(code_challenge_s256, client.code_challenge)
|
||||
|
|
|
@ -1,20 +1,12 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import absolute_import, unicode_literals
|
||||
|
||||
import os
|
||||
|
||||
from mock import patch
|
||||
import urllib.parse as urlparse
|
||||
from unittest.mock import patch
|
||||
|
||||
from oauthlib import signals
|
||||
from oauthlib.oauth2 import LegacyApplicationClient
|
||||
|
||||
from ....unittest import TestCase
|
||||
|
||||
# this is the same import method used in oauthlib/oauth2/rfc6749/parameters.py
|
||||
try:
|
||||
import urlparse
|
||||
except ImportError:
|
||||
import urllib.parse as urlparse
|
||||
from tests.unittest import TestCase
|
||||
|
||||
|
||||
@patch('time.time', new=lambda: 1000)
|
||||
|
@ -32,7 +24,7 @@ class LegacyApplicationClientTest(TestCase):
|
|||
password = "user_password"
|
||||
body = "not=empty"
|
||||
|
||||
body_up = "not=empty&grant_type=password&username=%s&password=%s" % (username, password)
|
||||
body_up = "not=empty&grant_type=password&username={}&password={}".format(username, password)
|
||||
body_kwargs = body_up + "&some=providers&require=extra+arguments"
|
||||
|
||||
token_json = ('{ "access_token":"2YotnFZFEjr1zCsicMWpAA",'
|
||||
|
@ -105,8 +97,8 @@ class LegacyApplicationClientTest(TestCase):
|
|||
|
||||
# scenario 1, default behavior to not include `client_id`
|
||||
r1 = client.prepare_request_body(username=self.username, password=self.password)
|
||||
self.assertIn(r1, ('grant_type=password&username=%s&password=%s' % (self.username, self.password, ),
|
||||
'grant_type=password&password=%s&username=%s' % (self.password, self.username, ),
|
||||
self.assertIn(r1, ('grant_type=password&username={}&password={}'.format(self.username, self.password),
|
||||
'grant_type=password&password={}&username={}'.format(self.password, self.username),
|
||||
))
|
||||
|
||||
# scenario 2, include `client_id` in the body
|
||||
|
|
|
@ -1,14 +1,11 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import absolute_import, unicode_literals
|
||||
|
||||
import os
|
||||
|
||||
from mock import patch
|
||||
from unittest.mock import patch
|
||||
|
||||
from oauthlib import signals
|
||||
from oauthlib.oauth2 import MobileApplicationClient
|
||||
|
||||
from ....unittest import TestCase
|
||||
from tests.unittest import TestCase
|
||||
|
||||
|
||||
@patch('time.time', new=lambda: 1000)
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue