summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPieter Ennes <pieter@ennes.nl>2018-05-26 20:39:09 +0100
committerGitHub <noreply@github.com>2018-05-26 20:39:09 +0100
commite44d5d93dd0d97998a58f1b84cb51119d136fad2 (patch)
treee0afe763750ffa7f608dc82f8a92b396f3be5922
parent296c6bc5931c95f631c1a496dacc523959fc50e9 (diff)
parentfedc1d1b740a0407ec59152750bbbd9dc736b51d (diff)
downloadoauthlib-e44d5d93dd0d97998a58f1b84cb51119d136fad2.tar.gz
Merge branch 'master' into oauth2-introspect
-rw-r--r--.coveragerc20
-rw-r--r--.travis.yml61
-rw-r--r--AUTHORS3
-rw-r--r--CHANGELOG.rst32
-rw-r--r--Makefile74
-rw-r--r--README.rst43
-rw-r--r--docs/conf.py5
-rw-r--r--docs/contributing.rst10
-rw-r--r--docs/faq.rst21
-rw-r--r--docs/index.rst2
-rw-r--r--docs/installation.rst2
-rw-r--r--docs/oauth1/preconfigured_servers.rst2
-rw-r--r--docs/oauth1/security.rst12
-rw-r--r--docs/oauth1/server.rst2
-rw-r--r--docs/oauth2/clients/client.rst2
-rw-r--r--docs/oauth2/endpoints/endpoints.rst2
-rw-r--r--docs/oauth2/grants/jwt.rst2
-rw-r--r--docs/oauth2/server.rst12
-rw-r--r--docs/oauth2/tokens/mac.rst2
-rw-r--r--docs/oauth2/tokens/saml.rst2
-rw-r--r--docs/oauth2/tokens/tokens.rst4
-rw-r--r--examples/skeleton_oauth2_web_application_server.py2
-rw-r--r--oauthlib/__init__.py4
-rw-r--r--oauthlib/common.py21
-rw-r--r--oauthlib/oauth1/rfc5849/__init__.py4
-rw-r--r--oauthlib/oauth1/rfc5849/endpoints/access_token.py2
-rw-r--r--oauthlib/oauth1/rfc5849/endpoints/base.py6
-rw-r--r--oauthlib/oauth1/rfc5849/endpoints/request_token.py4
-rw-r--r--oauthlib/oauth1/rfc5849/endpoints/resource.py2
-rw-r--r--oauthlib/oauth1/rfc5849/parameters.py22
-rw-r--r--oauthlib/oauth1/rfc5849/request_validator.py6
-rw-r--r--oauthlib/oauth1/rfc5849/signature.py86
-rw-r--r--oauthlib/oauth1/rfc5849/utils.py2
-rw-r--r--oauthlib/oauth2/rfc6749/clients/backend_application.py6
-rw-r--r--oauthlib/oauth2/rfc6749/clients/base.py34
-rw-r--r--oauthlib/oauth2/rfc6749/clients/legacy_application.py6
-rw-r--r--oauthlib/oauth2/rfc6749/clients/mobile_application.py16
-rw-r--r--oauthlib/oauth2/rfc6749/clients/service_application.py6
-rw-r--r--oauthlib/oauth2/rfc6749/clients/web_application.py18
-rw-r--r--oauthlib/oauth2/rfc6749/endpoints/authorization.py2
-rw-r--r--oauthlib/oauth2/rfc6749/endpoints/pre_configured.py7
-rw-r--r--oauthlib/oauth2/rfc6749/endpoints/resource.py2
-rw-r--r--oauthlib/oauth2/rfc6749/endpoints/revocation.py12
-rw-r--r--oauthlib/oauth2/rfc6749/endpoints/token.py2
-rw-r--r--oauthlib/oauth2/rfc6749/grant_types/authorization_code.py29
-rw-r--r--oauthlib/oauth2/rfc6749/grant_types/client_credentials.py6
-rw-r--r--oauthlib/oauth2/rfc6749/grant_types/implicit.py34
-rw-r--r--oauthlib/oauth2/rfc6749/grant_types/refresh_token.py8
-rw-r--r--oauthlib/oauth2/rfc6749/grant_types/resource_owner_password_credentials.py10
-rw-r--r--oauthlib/oauth2/rfc6749/parameters.py32
-rw-r--r--oauthlib/oauth2/rfc6749/request_validator.py74
-rw-r--r--oauthlib/oauth2/rfc6749/tokens.py82
-rw-r--r--oauthlib/signals.py2
-rw-r--r--requirements-test.txt2
-rw-r--r--requirements.txt6
-rwxr-xr-xsetup.py4
-rw-r--r--tests/oauth2/rfc6749/clients/test_base.py72
-rw-r--r--tests/oauth2/rfc6749/clients/test_mobile_application.py12
-rw-r--r--tests/oauth2/rfc6749/clients/test_service_application.py70
-rw-r--r--tests/oauth2/rfc6749/clients/test_web_application.py21
-rw-r--r--tests/oauth2/rfc6749/test_tokens.py209
-rw-r--r--tox.ini12
62 files changed, 949 insertions, 323 deletions
diff --git a/.coveragerc b/.coveragerc
new file mode 100644
index 0000000..70666c7
--- /dev/null
+++ b/.coveragerc
@@ -0,0 +1,20 @@
+[run]
+branch = 1
+cover_pylib = 0
+include=*oauthlib/*
+omit = oauthlib.tests.*
+
+[report]
+omit =
+ */python?.?/*
+ */site-packages/*
+ */pypy/*
+exclude_lines =
+ pragma: no cover
+ def __repr__
+ if __debug__:
+ raise AssertionError
+ raise NotImplementedError
+ if 0:
+ if __name__ == .__main__.:
+ noqa
diff --git a/.travis.yml b/.travis.yml
index f5e9aca..dd72d5c 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,34 +1,47 @@
language: python
sudo: false
cache: pip
-
matrix:
include:
- - python: 2.7
- env: TOXENV=py27
- - python: 3.4
- env: TOXENV=py34
- - python: 3.5
- env: TOXENV=py35
- - python: 3.6
- env: TOXENV=py36
- - python: pypy-5.3
- env: TOXENV=pypy
-
+ - python: 2.7
+ env: TOXENV=py27
+ - python: 3.4
+ env: TOXENV=py34
+ - python: 3.5
+ env: TOXENV=py35
+ - python: 3.6
+ env: TOXENV=py36
+ - python: pypy-5.3
+ env: TOXENV=pypy
install:
- - pip install -U setuptools
- - pip install tox coveralls
+- pip install -U setuptools
+- pip install tox coveralls
script: tox
-
after_success: coveralls
notifications:
- irc: irc.freenode.org#oauthlib
+ webhooks:
+ urls:
+ - https://webhooks.gitter.im/e/6008c872bf0ecee344f4
+ on_success: change
+ on_failure: always
+ on_start: never
deploy:
- provider: pypi
- user: ib.lundgren
- password:
- secure: PGZF9pRiTGCSwQjk1ddTKF3x4rQ0iAiPbg2uSixyO68uMXRgJjwHhSrNM0OEqtK5YWU5FE5L0DwR1nkrpEJKO4a5q2EOgos+gVoKpJfinoUNOOkjc1VHpqKM0uRf/OKrw1alvWUwqvW8B+DOb9TY5c5VZxQuRL+iwdrtwzFlKls=
- distributions: sdist bdist_wheel
- on:
- tags: true
- repo: idan/oauthlib
+ - provider: pypi
+ user: JonathanHuot
+ password:
+ secure: "OozNM16flVLvqDoNzmoTENchhS1w0/dEJZvXBQK2KWmh8fyGj2UZus1vkl6bA5V3Yu9MZLYFpDcltl/qraY3Up6iXQpwKz4q+ICygAudYM2kJ5l8ZEe+wy2FikWbD6LkXf5uKIJJnPNSC8AI86ZyxM/XZxbYjj/+jXyJ1YFZwwQ="
+ distributions: sdist bdist_wheel
+ on:
+ tags: true
+ all_branches: true
+ condition: $TOXENV = py36
+ repo: oauthlib/oauthlib
+ - provider: releases
+ api_key:
+ secure: LEzTaeQt4+Sp21t7usmwaEYLThKIGWDNNj04JADMLgfquTeyz5nDu9P8JNlT//G9RNN20oR8w7jZo97Y+JAylq6Hh/I+p/MEzZi8+NwIpObk3n3zJO4witZQQSTEw/6B7qf1/NQQxjQzlYTJjsGXxBps7srviWZmbH6Tz+epA3A=
+ skip_cleanup: true
+ on:
+ tags: true
+ all_branches: true
+ condition: $TOXENV = py36
+ repo: oauthlib/oauthlib
diff --git a/AUTHORS b/AUTHORS
index 811679e..f52ce9a 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -26,3 +26,6 @@ Juan Fabio GarcĂ­a Solero
Omer Katz
Joel Stevenson
Brendan McCollam
+Jonathan Huot
+Pieter Ennes
+Olaf Conradi
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index 8a20f92..a8e1941 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -1,6 +1,38 @@
Changelog
=========
+2.1.0 (2018-05-21)
+------------------
+
+* Fixed some copy and paste typos (#535)
+* Use secrets module in Python 3.6 and later (#533)
+* Add request argument to confirm_redirect_uri (#504)
+* Avoid populating spurious token credentials (#542)
+* Make populate attributes API public (#546)
+
+2.0.7 (2018-03-19)
+------------------
+
+* Moved oauthlib into new organization on GitHub.
+* Include license file in the generated wheel package. (#494)
+* When deploying a release to PyPI, include the wheel distribution. (#496)
+* Check access token in self.token dict. (#500)
+* Added bottle-oauthlib to docs. (#509)
+* Update repository location in Travis. (#514)
+* Updated docs for organization change. (#515)
+* Replace G+ with Gitter. (#517)
+* Update requirements. (#518)
+* Add shields for Python versions, license and RTD. (#520)
+* Fix ReadTheDocs build (#521).
+* Fixed "make" command to test upstream with local oauthlib. (#522)
+* Replace IRC notification with Gitter Hook. (#523)
+* Added Github Releases deploy provider. (#523)
+
+2.0.6 (2017-10-20)
+------------------
+
+* 2.0.5 contains breaking changes.
+
2.0.5 (2017-10-19)
------------------
diff --git a/Makefile b/Makefile
index 259fe9c..8571a91 100644
--- a/Makefile
+++ b/Makefile
@@ -1,47 +1,57 @@
-PYS = py27,py34,pypy
+# Downstream tests (Don't be evil)
+#
+# Try and not break the libraries below by running their tests too.
+#
+# Unfortunately there is no neat way to run downstream tests AFAIK
+# Until we have a proper downstream testing system we will
+# stick to this Makefile.
+#---------------------------
+# HOW TO ADD NEW DOWNSTREAM LIBRARIES
+#
+# Please specify your library as well as primary contacts.
+# Since these contacts will be addressed with Github mentions they
+# need to be Github users (for now)(sorry Bitbucket).
+#
+clean:
+ rm -rf .tox
+ rm -rf bottle-oauthlib
+ rm -rf django-oauth-toolkit
+ rm -rf flask-oauthlib
+ rm -rf requests-oauthlib
test:
- # Test OAuthLib
- tox -e "$(PYS)"
- #
- # Downstream tests (Don't be evil)
- #
- # Try and not break the libraries below by running their tests too.
- #
- # Unfortunately there is no neat way to run downstream tests AFAIK
- # Until we have a proper downstream testing system we will
- # stick to this Makefile.
+ tox
+
+bottle:
#---------------------------
- # HOW TO ADD NEW DOWNSTREAM LIBRARIES
- #
- # Please specify your library as well as primary contacts.
- # Since these contacts will be addressed with Github mentions they
- # need to be Github users (for now)(sorry Bitbucket).
- #
+ # Library thomsonreuters/bottle-oauthlib
+ # Contacts: Jonathan.Huot
+ cd bottle-oauthlib 2>/dev/null || git clone https://github.com/thomsonreuters/bottle-oauthlib.git
+ cd bottle-oauthlib && sed -i.old 's,deps =,deps= --editable=file://{toxinidir}/../,' tox.ini && sed -i.old '/oauthlib/d' requirements.txt && tox
+
+flask:
#---------------------------
# Library: lepture/flask-oauthLib
# Contacts: lepture,widnyana
- git clone https://github.com/lepture/flask-oauthlib.git
- cd flask-oauthlib && cp ../tox.ini . && sed -i 's/py32,py33,py34,//' tox.ini && sed -i '/mock/a \ Flask-SQLAlchemy' tox.ini && tox -e "$(PYS)"
- rm -rf flask-oauthlib
+ cd flask-oauthlib 2>/dev/null || git clone https://github.com/lepture/flask-oauthlib.git
+ cd flask-oauthlib && sed -i.old 's,deps =,deps= --editable=file://{toxinidir}/../,' tox.ini && sed -i.old '/oauthlib/d' requirements.txt && tox
+
+django:
#---------------------------
# Library: evonove/django-oauth-toolkit
# Contacts: evonove,masci
# (note: has tox.ini already)
- git clone https://github.com/evonove/django-oauth-toolkit.git
- cd django-oauth-toolkit && tox -e "$(PYS)"
- rm -rf django-oauth-toolkit
+ cd django-oauth-toolkit 2>/dev/null || git clone https://github.com/evonove/django-oauth-toolkit.git
+ cd django-oauth-toolkit && sed -i.old 's,deps =,deps= --editable=file://{toxinidir}/../,' tox.ini && tox -e py27,py35,py36
+
+requests:
#---------------------------
# Library requests/requests-oauthlib
# Contacts: ib-lundgren,lukasa
- git clone https://github.com/requests/requests-oauthlib.git
- cd requests-oauthlib && cp ../tox.ini . && sed -i '/mock/a \ requests' tox.ini && tox -e "$(PYS)"
- rm -rf requests-oauthlib
- #---------------------------
- #
+ cd requests-oauthlib 2>/dev/null || git clone https://github.com/requests/requests-oauthlib.git
+ cd requests-oauthlib && sed -i.old 's,deps=,deps = --editable=file://{toxinidir}/../[signedtoken],' tox.ini && sed -i.old '/oauthlib/d' requirements.txt && tox
-pycco:
- find oauthlib -name "*.py" -exec pycco -p -s reST {} \;
-pycco-clean:
- rm -rf docs/oauthlib docs/pycco.css
+.DEFAULT_GOAL := all
+.PHONY: clean test bottle django flask requests
+all: clean test bottle django flask requests
diff --git a/README.rst b/README.rst
index eb85ffa..b477e41 100644
--- a/README.rst
+++ b/README.rst
@@ -2,13 +2,26 @@ OAuthLib
========
*A generic, spec-compliant, thorough implementation of the OAuth request-signing
-logic for python*
-
-.. image:: https://travis-ci.org/idan/oauthlib.svg?branch=master
- :target: https://travis-ci.org/idan/oauthlib
-.. image:: https://coveralls.io/repos/idan/oauthlib/badge.svg?branch=master
- :target: https://coveralls.io/r/idan/oauthlib
-
+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.python.org/pypi/oauthlib
+ :alt: Download from PyPi
+.. image:: https://img.shields.io/pypi/l/oauthlib.svg
+ :target: https://pypi.python.org/pypi/oauthlib
+ :alt: License
+.. 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
@@ -18,8 +31,8 @@ both of the following:
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`: http://tools.ietf.org/html/rfc5849
-.. _`OAuth 2.0 spec`: http://tools.ietf.org/html/rfc6749
+.. _`OAuth 1.0 spec`: https://tools.ietf.org/html/rfc5849
+.. _`OAuth 2.0 spec`: https://tools.ietf.org/html/rfc6749
OAuthLib is a generic utility which implements the logic of OAuth without
assuming a specific HTTP request object or web framework. Use it to graft OAuth
@@ -33,10 +46,10 @@ 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 `G+ community`_, or even better, send a
+you'd like to know, or discuss it in our `Gitter community`_, or even better, send a
pull request!
-.. _`G+ community`: https://plus.google.com/communities/101889017375384052571
+.. _`Gitter community`: https://gitter.im/oauthlib/Lobby
.. _`Read the Docs`: https://oauthlib.readthedocs.io/en/latest/index.html
Interested in making OAuth requests?
@@ -45,7 +58,7 @@ 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/kennethreitz/requests
+.. _`requests`: https://github.com/requests/requests
.. _`requests-oauthlib`: https://github.com/requests/requests-oauthlib
Which web frameworks are supported?
@@ -56,6 +69,7 @@ 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.
@@ -65,6 +79,7 @@ please open a Pull Request, updating the documentation.
.. _`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!
------------------------------------
@@ -72,7 +87,7 @@ 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 `G+ community`_ or if you have
+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!
@@ -81,7 +96,7 @@ 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 =)
-.. _`G+ community`: https://plus.google.com/communities/101889017375384052571
+.. _`Gitter community`: https://gitter.im/oauthlib/Lobby
License
-------
diff --git a/docs/conf.py b/docs/conf.py
index fb14d05..017f686 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -14,8 +14,6 @@
import os
import sys
-from oauthlib import __version__ as v
-
# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
@@ -51,6 +49,7 @@ copyright = u'2012, Idan Gazit and the Python Community'
#
# The short X.Y version.
+from oauthlib import __version__ as v
version = v[:3]
# The full version, including alpha/beta/rc tags.
release = v
@@ -243,3 +242,5 @@ texinfo_documents = [
# How to display URL addresses: 'footnote', 'no', or 'inline'.
#texinfo_show_urls = 'footnote'
+
+linkcheck_ignore = ["https://github.com/oauthlib/oauthlib/issues/new"]
diff --git a/docs/contributing.rst b/docs/contributing.rst
index f3de44d..601c567 100644
--- a/docs/contributing.rst
+++ b/docs/contributing.rst
@@ -91,7 +91,7 @@ request only to have it rejected because it has diverged too far from master.
To pull in upstream changes::
- git remote add upstream https://github.com/idan/oauthlib.git
+ git remote add upstream https://github.com/oauthlib/oauthlib.git
git fetch upstream
Check the log to be sure that you actually want the changes, before merging::
@@ -102,7 +102,7 @@ Then merge the changes that you fetched::
git merge upstream/master
-For more info, see http://help.github.com/fork-a-repo/
+For more info, see https://help.github.com/fork-a-repo/
How to get your pull request accepted
=====================================
@@ -148,7 +148,7 @@ version. For Ubuntu you can easily install all after adding one ppa.
$ sudo apt-get install pypy pypy-dev
.. _`Tox`: https://tox.readthedocs.io/en/latest/install.html
-.. _`virtualenv`: http://www.virtualenv.org/en/latest/#installation
+.. _`virtualenv`: https://virtualenv.pypa.io/en/latest/installation/
If you add code you need to add tests!
--------------------------------------
@@ -223,5 +223,5 @@ to GitHub::
git push upstream master
.. _installation: install.html
-.. _GitHub project: https://github.com/idan/oauthlib
-.. _issue tracker: https://github.com/idan/oauthlib/issues
+.. _GitHub project: https://github.com/oauthlib/oauthlib
+.. _issue tracker: https://github.com/oauthlib/oauthlib/issues
diff --git a/docs/faq.rst b/docs/faq.rst
index 4d896f5..38b0e92 100644
--- a/docs/faq.rst
+++ b/docs/faq.rst
@@ -65,10 +65,17 @@ How do I use OAuthLib with Google, Twitter and other providers?
How do I use OAuthlib as a provider with Django, Flask and other web frameworks?
--------------------------------------------------------------------------------
- Providers using Django should seek out `django-oauth-toolkit`_
- and those using Flask `flask-oauthlib`_. For other frameworks,
- please get in touch by opening a `GitHub issue`_, on `G+`_ or
- on IRC #oauthlib irc.freenode.net.
+ Providers can be implemented in any web frameworks. However, some of
+ them have ready-to-use libraries to help integration:
+ - Django `django-oauth-toolkit`_
+ - Flask `flask-oauthlib`_
+ - Pyramid `pyramid-oauthlib`_
+ - Bottle `bottle-oauthlib`_
+
+ For other frameworks, please get in touch by opening a `GitHub issue`_, on `G+`_ or
+ on IRC #oauthlib irc.freenode.net. If you have written an OAuthLib package that
+ supports your favorite framework, please open a Pull Request to update the docs.
+
What is the difference between authentication and authorization?
----------------------------------------------------------------
@@ -91,6 +98,8 @@ Some argue OAuth 2 is worse than 1, is that true?
.. _`requests-oauthlib`: https://github.com/requests/requests-oauthlib
.. _`django-oauth-toolkit`: https://github.com/evonove/django-oauth-toolkit
.. _`flask-oauthlib`: https://github.com/lepture/flask-oauthlib
-.. _`GitHub issue`: https://github.com/idan/oauthlib/issues/new
+.. _`pyramid-oauthlib`: https://github.com/tilgovi/pyramid-oauthlib
+.. _`bottle-oauthlib`: https://github.com/thomsonreuters/bottle-oauthlib
+.. _`GitHub issue`: https://github.com/oauthlib/oauthlib/issues/new
.. _`G+`: https://plus.google.com/communities/101889017375384052571
-.. _`difference`: http://www.cyberciti.biz/faq/authentication-vs-authorization/
+.. _`difference`: https://www.cyberciti.biz/faq/authentication-vs-authorization/
diff --git a/docs/index.rst b/docs/index.rst
index 1699068..1da2ca5 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -13,7 +13,7 @@ Check out :doc:`error_reporting` for details on how to be an awesome bug reporte
For news and discussions please head over to our `G+ OAuthLib community`_.
-.. _`new issue on GitHub`: https://github.com/idan/oauthlib/issues/new
+.. _`new issue on GitHub`: https://github.com/oauthlib/oauthlib/issues/new
.. _`G+ OAuthLib community`: https://plus.google.com/communities/101889017375384052571
.. toctree::
diff --git a/docs/installation.rst b/docs/installation.rst
index 5a8b2cb..48e4288 100644
--- a/docs/installation.rst
+++ b/docs/installation.rst
@@ -22,7 +22,7 @@ Bleeding edge from GitHub master
.. code-block:: bash
- pip install -e git+https://github.com/idan/oauthlib.git#egg=oauthlib
+ pip install -e git+https://github.com/oauthlib/oauthlib.git#egg=oauthlib
Debian and derivatives like Ubuntu, Mint, etc.
---------------------------------------------
diff --git a/docs/oauth1/preconfigured_servers.rst b/docs/oauth1/preconfigured_servers.rst
index 7f7f386..b32e1ab 100644
--- a/docs/oauth1/preconfigured_servers.rst
+++ b/docs/oauth1/preconfigured_servers.rst
@@ -12,7 +12,7 @@ Construction is simple, only import your validator and you are good to go::
server = WebApplicationServer(your_validator)
-All endpoints are documented in :doc:`endpoints`.
+All endpoints are documented in :doc:`Provider endpoints <endpoints/endpoints>`.
.. autoclass:: oauthlib.oauth1.WebApplicationServer
:members:
diff --git a/docs/oauth1/security.rst b/docs/oauth1/security.rst
index a1432a9..df1e2a0 100644
--- a/docs/oauth1/security.rst
+++ b/docs/oauth1/security.rst
@@ -16,11 +16,13 @@ A few important facts regarding OAuth security
* **Tokens must be random**, OAuthLib provides a method for generating
secure tokens and it's packed into ``oauthlib.common.generate_token``,
- use it. If you decide to roll your own, use ``random.SystemRandom``
- which is based on ``os.urandom`` rather than the default ``random``
- based on the effecient but not truly random Mersenne Twister.
- Predictable tokens allow attackers to bypass virtually all defences
- OAuth provides.
+ use it. If you decide to roll your own, use ``secrets.SystemRandom``
+ for Python 3.6 and later. The ``secrets`` module is designed for
+ generating cryptographically strong random numbers. For earlier versions
+ of Python, use ``random.SystemRandom`` which is based on ``os.urandom``
+ rather than the default ``random`` based on the effecient but not truly
+ random Mersenne Twister. Predictable tokens allow attackers to bypass
+ virtually all defences OAuth provides.
* **Timing attacks are real** and more than possible if you host your
application inside a shared datacenter. Ensure all ``validate_`` methods
diff --git a/docs/oauth1/server.rst b/docs/oauth1/server.rst
index f254c91..2a91f30 100644
--- a/docs/oauth1/server.rst
+++ b/docs/oauth1/server.rst
@@ -436,7 +436,7 @@ shown below as well as run your flask server locally on port `5000`.
Drop a line in our `G+ community`_ or open a `GitHub issue`_ =)
.. _`G+ community`: https://plus.google.com/communities/101889017375384052571
-.. _`GitHub issue`: https://github.com/idan/oauthlib/issues/new
+.. _`GitHub issue`: https://github.com/oauthlib/oauthlib/issues/new
If you run into issues it can be helpful to enable debug logging::
diff --git a/docs/oauth2/clients/client.rst b/docs/oauth2/clients/client.rst
index 11da2cc..9a5a4ff 100644
--- a/docs/oauth2/clients/client.rst
+++ b/docs/oauth2/clients/client.rst
@@ -24,5 +24,5 @@ to use them please browse the documentation for each client type below.
If you are interested in integrating OAuth 2 support into your favourite
HTTP library you might find the requests-oauthlib implementation interesting.
- .. _`requests`: https://github.com/kennethreitz/requests
+ .. _`requests`: https://github.com/requests/requests
.. _`requests-oauthlib`: https://github.com/requests/requests-oauthlib
diff --git a/docs/oauth2/endpoints/endpoints.rst b/docs/oauth2/endpoints/endpoints.rst
index 5f7ae8c..80d5fbe 100644
--- a/docs/oauth2/endpoints/endpoints.rst
+++ b/docs/oauth2/endpoints/endpoints.rst
@@ -24,7 +24,7 @@ handles user authorization, the token endpoint which provides tokens and the
resource endpoint which provides access to protected resources. It is to the
endpoints you will feed requests and get back an almost complete response. This
process is simplified for you using a decorator such as the django one described
-later.
+later (but it's applicable to all other web frameworks librairies).
The main purpose of the endpoint in OAuthLib is to figure out which grant type
or token to dispatch the request to.
diff --git a/docs/oauth2/grants/jwt.rst b/docs/oauth2/grants/jwt.rst
index 87aed11..db65342 100644
--- a/docs/oauth2/grants/jwt.rst
+++ b/docs/oauth2/grants/jwt.rst
@@ -4,4 +4,4 @@ JWT Tokens
Not yet implemented. Track progress in `GitHub issue 50`_.
-.. _`GitHub issue 50`: https://github.com/idan/oauthlib/issues/50
+.. _`GitHub issue 50`: https://github.com/oauthlib/oauthlib/issues/50
diff --git a/docs/oauth2/server.rst b/docs/oauth2/server.rst
index 9d6b502..8f8b77b 100644
--- a/docs/oauth2/server.rst
+++ b/docs/oauth2/server.rst
@@ -6,8 +6,10 @@ OAuthLib is a dependency free library that may be used with any web
framework. That said, there are framework specific helper libraries
to make your life easier.
-- For Django there is `django-oauth-toolkit`_.
-- For Flask there is `flask-oauthlib`_.
+- Django `django-oauth-toolkit`_
+- Flask `flask-oauthlib`_
+- Pyramid `pyramid-oauthlib`_
+- Bottle `bottle-oauthlib`_
If there is no support for your favourite framework and you are interested
in providing it then you have come to the right place. OAuthLib can handle
@@ -17,6 +19,8 @@ as well as provide an interface for a backend to store tokens, clients, etc.
.. _`django-oauth-toolkit`: https://github.com/evonove/django-oauth-toolkit
.. _`flask-oauthlib`: https://github.com/lepture/flask-oauthlib
+.. _`pyramid-oauthlib`: https://github.com/tilgovi/pyramid-oauthlib
+.. _`bottle-oauthlib`: https://github.com/thomsonreuters/bottle-oauthlib
.. contents:: Tutorial Contents
:depth: 3
@@ -275,7 +279,7 @@ all methods depending on which grant types you wish to support. A skeleton
validator listing the methods required for the WebApplicationServer is
available in the `examples`_ folder on GitHub.
-.. _`examples`: https://github.com/idan/oauthlib/blob/master/examples/skeleton_oauth2_web_application_server.py
+.. _`examples`: https://github.com/oauthlib/oauthlib/blob/master/examples/skeleton_oauth2_web_application_server.py
Relevant sections include:
@@ -492,7 +496,7 @@ at runtime by a function, rather then by a list.
Drop a line in our `G+ community`_ or open a `GitHub issue`_ =)
.. _`G+ community`: https://plus.google.com/communities/101889017375384052571
-.. _`GitHub issue`: https://github.com/idan/oauthlib/issues/new
+.. _`GitHub issue`: https://github.com/oauthlib/oauthlib/issues/new
If you run into issues it can be helpful to enable debug logging.
diff --git a/docs/oauth2/tokens/mac.rst b/docs/oauth2/tokens/mac.rst
index 4986819..afb6948 100644
--- a/docs/oauth2/tokens/mac.rst
+++ b/docs/oauth2/tokens/mac.rst
@@ -5,4 +5,4 @@ MAC tokens
Not yet implemented. Track progress in `GitHub issue 29`_. Might never be
supported depending on whether the work on the specification is resumed or not.
-.. _`GitHub issue 29`: https://github.com/idan/oauthlib/issues/29
+.. _`GitHub issue 29`: https://github.com/oauthlib/oauthlib/issues/29
diff --git a/docs/oauth2/tokens/saml.rst b/docs/oauth2/tokens/saml.rst
index 9a00937..5faf16a 100644
--- a/docs/oauth2/tokens/saml.rst
+++ b/docs/oauth2/tokens/saml.rst
@@ -4,4 +4,4 @@ SAML Tokens
Not yet implemented. Track progress in `GitHub issue 49`_.
-.. _`GitHub issue 49`: https://github.com/idan/oauthlib/issues/49
+.. _`GitHub issue 49`: https://github.com/oauthlib/oauthlib/issues/49
diff --git a/docs/oauth2/tokens/tokens.rst b/docs/oauth2/tokens/tokens.rst
index f0adc97..f341509 100644
--- a/docs/oauth2/tokens/tokens.rst
+++ b/docs/oauth2/tokens/tokens.rst
@@ -15,8 +15,8 @@ providers, notably Facebook, do not provide this information. Per the
is missing. You can force a ``MissingTokenTypeError`` exception instead, by
setting ``OAUTHLIB_STRICT_TOKEN_TYPE`` in the environment.
-.. _requires: http://tools.ietf.org/html/rfc6749#section-5.1
-.. _robustness principle: http://en.wikipedia.org/wiki/Robustness_principle
+.. _requires: https://tools.ietf.org/html/rfc6749#section-5.1
+.. _robustness principle: https://en.wikipedia.org/wiki/Robustness_principle
.. toctree::
:maxdepth: 2
diff --git a/examples/skeleton_oauth2_web_application_server.py b/examples/skeleton_oauth2_web_application_server.py
index 8bfd936..e53232f 100644
--- a/examples/skeleton_oauth2_web_application_server.py
+++ b/examples/skeleton_oauth2_web_application_server.py
@@ -67,7 +67,7 @@ class SkeletonValidator(RequestValidator):
# state and user to request.scopes and request.user.
pass
- def confirm_redirect_uri(self, client_id, code, redirect_uri, client, *args, **kwargs):
+ def confirm_redirect_uri(self, client_id, code, redirect_uri, client, request, *args, **kwargs):
# You did save the redirect uri with the authorization code right?
pass
diff --git a/oauthlib/__init__.py b/oauthlib/__init__.py
index 459f307..3393efe 100644
--- a/oauthlib/__init__.py
+++ b/oauthlib/__init__.py
@@ -9,8 +9,8 @@
:license: BSD, see LICENSE for details.
"""
-__author__ = 'Idan Gazit <idan@gazit.me>'
-__version__ = '2.0.5'
+__author__ = 'The OAuthlib Community'
+__version__ = '2.1.0'
import logging
diff --git a/oauthlib/common.py b/oauthlib/common.py
index 705cbd2..f25656f 100644
--- a/oauthlib/common.py
+++ b/oauthlib/common.py
@@ -11,12 +11,17 @@ from __future__ import absolute_import, unicode_literals
import collections
import datetime
import logging
-import random
import re
import sys
import time
try:
+ from secrets import randbits
+ from secrets import SystemRandom
+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
@@ -199,10 +204,10 @@ def generate_nonce():
A random 64-bit number is appended to the epoch timestamp for both
randomness and to decrease the likelihood of collisions.
- .. _`section 3.2.1`: http://tools.ietf.org/html/draft-ietf-oauth-v2-http-mac-01#section-3.2.1
- .. _`section 3.3`: http://tools.ietf.org/html/rfc5849#section-3.3
+ .. _`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(random.getrandbits(64)) + generate_timestamp())
+ return unicode_type(unicode_type(randbits(64)) + generate_timestamp())
def generate_timestamp():
@@ -211,8 +216,8 @@ def generate_timestamp():
Per `section 3.3`_ of the OAuth 1 RFC 5849 spec.
Per `section 3.2.1`_ of the MAC Access Authentication spec.
- .. _`section 3.2.1`: http://tools.ietf.org/html/draft-ietf-oauth-v2-http-mac-01#section-3.2.1
- .. _`section 3.3`: http://tools.ietf.org/html/rfc5849#section-3.3
+ .. _`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()))
@@ -225,7 +230,7 @@ def generate_token(length=30, chars=UNICODE_ASCII_CHARACTER_SET):
and entropy when generating the random characters is important. Which is
why SystemRandom is used instead of the default random.choice method.
"""
- rand = random.SystemRandom()
+ rand = SystemRandom()
return ''.join(rand.choice(chars) for x in range(length))
@@ -257,7 +262,7 @@ def generate_client_id(length=30, chars=CLIENT_ID_CHARACTER_SET):
"""Generates an OAuth client_id
OAuth 2 specify the format of client_id in
- http://tools.ietf.org/html/rfc6749#appendix-A.
+ https://tools.ietf.org/html/rfc6749#appendix-A.
"""
return generate_token(length, chars)
diff --git a/oauthlib/oauth1/rfc5849/__init__.py b/oauthlib/oauth1/rfc5849/__init__.py
index f9113ab..87a8e6b 100644
--- a/oauthlib/oauth1/rfc5849/__init__.py
+++ b/oauthlib/oauth1/rfc5849/__init__.py
@@ -122,7 +122,7 @@ class Client(object):
replace any netloc part of the request argument's uri attribute
value.
- .. _`section 3.4.1.2`: http://tools.ietf.org/html/rfc5849#section-3.4.1.2
+ .. _`section 3.4.1.2`: https://tools.ietf.org/html/rfc5849#section-3.4.1.2
"""
if self.signature_method == SIGNATURE_PLAINTEXT:
# fast-path
@@ -300,7 +300,7 @@ class Client(object):
raise ValueError(
'Body signatures may only be used with form-urlencoded content')
- # We amend http://tools.ietf.org/html/rfc5849#section-3.4.1.3.1
+ # We amend https://tools.ietf.org/html/rfc5849#section-3.4.1.3.1
# with the clause that parameters from body should only be included
# in non GET or HEAD requests. Extracting the request body parameters
# and including them in the signature base string would give semantic
diff --git a/oauthlib/oauth1/rfc5849/endpoints/access_token.py b/oauthlib/oauth1/rfc5849/endpoints/access_token.py
index 12b901c..12d13e9 100644
--- a/oauthlib/oauth1/rfc5849/endpoints/access_token.py
+++ b/oauthlib/oauth1/rfc5849/endpoints/access_token.py
@@ -180,7 +180,7 @@ class AccessTokenEndpoint(BaseEndpoint):
# token credentials to the client, and ensure that the temporary
# credentials have not expired or been used before. The server MUST
# also verify the verification code received from the client.
- # .. _`Section 3.2`: http://tools.ietf.org/html/rfc5849#section-3.2
+ # .. _`Section 3.2`: https://tools.ietf.org/html/rfc5849#section-3.2
#
# Note that early exit would enable resource owner authorization
# verifier enumertion.
diff --git a/oauthlib/oauth1/rfc5849/endpoints/base.py b/oauthlib/oauth1/rfc5849/endpoints/base.py
index 9d51e69..9702939 100644
--- a/oauthlib/oauth1/rfc5849/endpoints/base.py
+++ b/oauthlib/oauth1/rfc5849/endpoints/base.py
@@ -127,7 +127,7 @@ class BaseEndpoint(object):
# specification. Implementers should review the Security
# Considerations section (`Section 4`_) before deciding on which
# method to support.
- # .. _`Section 4`: http://tools.ietf.org/html/rfc5849#section-4
+ # .. _`Section 4`: https://tools.ietf.org/html/rfc5849#section-4
if (not request.signature_method in
self.request_validator.allowed_signature_methods):
raise errors.InvalidSignatureMethodError(
@@ -181,7 +181,7 @@ class BaseEndpoint(object):
# ---- RSA Signature verification ----
if request.signature_method == SIGNATURE_RSA:
# The server verifies the signature per `[RFC3447] section 8.2.2`_
- # .. _`[RFC3447] section 8.2.2`: http://tools.ietf.org/html/rfc3447#section-8.2.1
+ # .. _`[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)
@@ -192,7 +192,7 @@ class BaseEndpoint(object):
# 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`: http://tools.ietf.org/html/rfc5849#section-3.4
+ # .. _`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
diff --git a/oauthlib/oauth1/rfc5849/endpoints/request_token.py b/oauthlib/oauth1/rfc5849/endpoints/request_token.py
index 515395b..88fd6c0 100644
--- a/oauthlib/oauth1/rfc5849/endpoints/request_token.py
+++ b/oauthlib/oauth1/rfc5849/endpoints/request_token.py
@@ -156,7 +156,7 @@ class RequestTokenEndpoint(BaseEndpoint):
# 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.
- # .. _`realm`: http://tools.ietf.org/html/rfc2617#section-1.2
+ # .. _`realm`: https://tools.ietf.org/html/rfc2617#section-1.2
#
# Note that early exit would enable client realm access enumeration.
#
@@ -178,7 +178,7 @@ class RequestTokenEndpoint(BaseEndpoint):
# Callback is normally never required, except for requests for
# a Temporary Credential as described in `Section 2.1`_
- # .._`Section 2.1`: http://tools.ietf.org/html/rfc5849#section-2.1
+ # .._`Section 2.1`: https://tools.ietf.org/html/rfc5849#section-2.1
valid_redirect = self.request_validator.validate_redirect_uri(
request.client_key, request.redirect_uri, request)
if not request.redirect_uri:
diff --git a/oauthlib/oauth1/rfc5849/endpoints/resource.py b/oauthlib/oauth1/rfc5849/endpoints/resource.py
index 53f9562..f82e8b1 100644
--- a/oauthlib/oauth1/rfc5849/endpoints/resource.py
+++ b/oauthlib/oauth1/rfc5849/endpoints/resource.py
@@ -119,7 +119,7 @@ class ResourceEndpoint(BaseEndpoint):
# 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.
- # .. _`realm`: http://tools.ietf.org/html/rfc2617#section-1.2
+ # .. _`realm`: https://tools.ietf.org/html/rfc2617#section-1.2
#
# Note that early exit would enable client realm access enumeration.
#
diff --git a/oauthlib/oauth1/rfc5849/parameters.py b/oauthlib/oauth1/rfc5849/parameters.py
index dcb23dc..db4400e 100644
--- a/oauthlib/oauth1/rfc5849/parameters.py
+++ b/oauthlib/oauth1/rfc5849/parameters.py
@@ -5,7 +5,7 @@ oauthlib.parameters
This module contains methods related to `section 3.5`_ of the OAuth 1.0a spec.
-.. _`section 3.5`: http://tools.ietf.org/html/rfc5849#section-3.5
+.. _`section 3.5`: https://tools.ietf.org/html/rfc5849#section-3.5
"""
from __future__ import absolute_import, unicode_literals
@@ -15,7 +15,7 @@ from . import utils
try:
from urlparse import urlparse, urlunparse
-except ImportError:
+except ImportError: # noqa
from urllib.parse import urlparse, urlunparse
@@ -42,8 +42,8 @@ def prepare_headers(oauth_params, headers=None, realm=None):
oauth_version="1.0"
- .. _`section 3.5.1`: http://tools.ietf.org/html/rfc5849#section-3.5.1
- .. _`RFC2617`: http://tools.ietf.org/html/rfc2617
+ .. _`section 3.5.1`: https://tools.ietf.org/html/rfc5849#section-3.5.1
+ .. _`RFC2617`: https://tools.ietf.org/html/rfc2617
"""
headers = headers or {}
@@ -54,7 +54,7 @@ def prepare_headers(oauth_params, headers=None, realm=None):
# 1. Parameter names and values are encoded per Parameter Encoding
# (`Section 3.6`_)
#
- # .. _`Section 3.6`: http://tools.ietf.org/html/rfc5849#section-3.6
+ # .. _`Section 3.6`: https://tools.ietf.org/html/rfc5849#section-3.6
escaped_name = utils.escape(oauth_parameter_name)
escaped_value = utils.escape(value)
@@ -68,14 +68,14 @@ def prepare_headers(oauth_params, headers=None, realm=None):
# 3. Parameters are separated by a "," character (ASCII code 44) and
# OPTIONAL linear whitespace per `RFC2617`_.
#
- # .. _`RFC2617`: http://tools.ietf.org/html/rfc2617
+ # .. _`RFC2617`: https://tools.ietf.org/html/rfc2617
authorization_header_parameters = ', '.join(
authorization_header_parameters_parts)
# 4. The OPTIONAL "realm" parameter MAY be added and interpreted per
# `RFC2617 section 1.2`_.
#
- # .. _`RFC2617 section 1.2`: http://tools.ietf.org/html/rfc2617#section-1.2
+ # .. _`RFC2617 section 1.2`: https://tools.ietf.org/html/rfc2617#section-1.2
if realm:
# NOTE: realm should *not* be escaped
authorization_header_parameters = ('realm="%s", ' % realm +
@@ -98,8 +98,8 @@ def _append_params(oauth_params, params):
Per `section 3.5.2`_ and `3.5.3`_ of the spec.
- .. _`section 3.5.2`: http://tools.ietf.org/html/rfc5849#section-3.5.2
- .. _`3.5.3`: http://tools.ietf.org/html/rfc5849#section-3.5.3
+ .. _`section 3.5.2`: https://tools.ietf.org/html/rfc5849#section-3.5.2
+ .. _`3.5.3`: https://tools.ietf.org/html/rfc5849#section-3.5.3
"""
merged = list(params)
@@ -117,7 +117,7 @@ def prepare_form_encoded_body(oauth_params, body):
Per `section 3.5.2`_ of the spec.
- .. _`section 3.5.2`: http://tools.ietf.org/html/rfc5849#section-3.5.2
+ .. _`section 3.5.2`: https://tools.ietf.org/html/rfc5849#section-3.5.2
"""
# append OAuth params to the existing body
@@ -129,7 +129,7 @@ def prepare_request_uri_query(oauth_params, uri):
Per `section 3.5.3`_ of the spec.
- .. _`section 3.5.3`: http://tools.ietf.org/html/rfc5849#section-3.5.3
+ .. _`section 3.5.3`: https://tools.ietf.org/html/rfc5849#section-3.5.3
"""
# append OAuth params to the existing set of query components
diff --git a/oauthlib/oauth1/rfc5849/request_validator.py b/oauthlib/oauth1/rfc5849/request_validator.py
index 2ccb367..bc62ea0 100644
--- a/oauthlib/oauth1/rfc5849/request_validator.py
+++ b/oauthlib/oauth1/rfc5849/request_validator.py
@@ -109,7 +109,7 @@ class RequestValidator(object):
their use more straightforward and as such it could be worth reading what
follows in chronological order.
- .. _`whitelisting or blacklisting`: http://www.schneier.com/blog/archives/2011/01/whitelisting_vs.html
+ .. _`whitelisting or blacklisting`: https://www.schneier.com/blog/archives/2011/01/whitelisting_vs.html
"""
def __init__(self):
@@ -445,7 +445,7 @@ class RequestValidator(object):
"The server MUST (...) ensure that the temporary
credentials have not expired or been used before."
- .. _`Section 2.3`: http://tools.ietf.org/html/rfc5849#section-2.3
+ .. _`Section 2.3`: https://tools.ietf.org/html/rfc5849#section-2.3
This method should ensure that provided token won't validate anymore.
It can be simply removing RequestToken from storage or setting
@@ -582,7 +582,7 @@ class RequestValidator(object):
channel. The nonce value MUST be unique across all requests with the
same timestamp, client credentials, and token combinations."
- .. _`Section 3.3`: http://tools.ietf.org/html/rfc5849#section-3.3
+ .. _`Section 3.3`: https://tools.ietf.org/html/rfc5849#section-3.3
One of the first validation checks that will be made is for the validity
of the nonce and timestamp, which are associated with a client key and
diff --git a/oauthlib/oauth1/rfc5849/signature.py b/oauthlib/oauth1/rfc5849/signature.py
index 30001ef..4e672ba 100644
--- a/oauthlib/oauth1/rfc5849/signature.py
+++ b/oauthlib/oauth1/rfc5849/signature.py
@@ -19,7 +19,7 @@ Steps for signing a request:
construct the base string
5. Pass the base string and any keys needed to a signing function
-.. _`section 3.4`: http://tools.ietf.org/html/rfc5849#section-3.4
+.. _`section 3.4`: https://tools.ietf.org/html/rfc5849#section-3.4
"""
from __future__ import absolute_import, unicode_literals
@@ -69,7 +69,7 @@ def construct_base_string(http_method, base_string_uri,
ethod%3DHMAC-SHA1%26oauth_timestamp%3D137131201%26oauth_token%3Dkkk
9d7dh3k39sjv7
- .. _`section 3.4.1.1`: http://tools.ietf.org/html/rfc5849#section-3.4.1.1
+ .. _`section 3.4.1.1`: https://tools.ietf.org/html/rfc5849#section-3.4.1.1
"""
# The signature base string is constructed by concatenating together,
@@ -79,7 +79,7 @@ def construct_base_string(http_method, base_string_uri,
# "GET", "POST", etc. If the request uses a custom HTTP method, it
# MUST be encoded (`Section 3.6`_).
#
- # .. _`Section 3.6`: http://tools.ietf.org/html/rfc5849#section-3.6
+ # .. _`Section 3.6`: https://tools.ietf.org/html/rfc5849#section-3.6
base_string = utils.escape(http_method.upper())
# 2. An "&" character (ASCII code 38).
@@ -88,8 +88,8 @@ def construct_base_string(http_method, base_string_uri,
# 3. The base string URI from `Section 3.4.1.2`_, after being encoded
# (`Section 3.6`_).
#
- # .. _`Section 3.4.1.2`: http://tools.ietf.org/html/rfc5849#section-3.4.1.2
- # .. _`Section 3.4.6`: http://tools.ietf.org/html/rfc5849#section-3.4.6
+ # .. _`Section 3.4.1.2`: https://tools.ietf.org/html/rfc5849#section-3.4.1.2
+ # .. _`Section 3.4.6`: https://tools.ietf.org/html/rfc5849#section-3.4.6
base_string += utils.escape(base_string_uri)
# 4. An "&" character (ASCII code 38).
@@ -98,8 +98,8 @@ def construct_base_string(http_method, base_string_uri,
# 5. The request parameters as normalized in `Section 3.4.1.3.2`_, after
# being encoded (`Section 3.6`).
#
- # .. _`Section 3.4.1.3.2`: http://tools.ietf.org/html/rfc5849#section-3.4.1.3.2
- # .. _`Section 3.4.6`: http://tools.ietf.org/html/rfc5849#section-3.4.6
+ # .. _`Section 3.4.1.3.2`: https://tools.ietf.org/html/rfc5849#section-3.4.1.3.2
+ # .. _`Section 3.4.6`: https://tools.ietf.org/html/rfc5849#section-3.4.6
base_string += utils.escape(normalized_encoded_request_parameters)
return base_string
@@ -123,7 +123,7 @@ def normalize_base_string_uri(uri, host=None):
is represented by the base string URI: "https://www.example.net:8080/".
- .. _`section 3.4.1.2`: http://tools.ietf.org/html/rfc5849#section-3.4.1.2
+ .. _`section 3.4.1.2`: https://tools.ietf.org/html/rfc5849#section-3.4.1.2
The host argument overrides the netloc part of the uri argument.
"""
@@ -137,7 +137,7 @@ def normalize_base_string_uri(uri, host=None):
# are included by constructing an "http" or "https" URI representing
# the request resource (without the query or fragment) as follows:
#
- # .. _`RFC3986`: http://tools.ietf.org/html/rfc3986
+ # .. _`RFC3986`: https://tools.ietf.org/html/rfc3986
if not scheme or not netloc:
raise ValueError('uri must include a scheme and netloc')
@@ -147,7 +147,7 @@ def normalize_base_string_uri(uri, host=None):
# Note that the absolute path cannot be empty; if none is present in
# the original URI, it MUST be given as "/" (the server root).
#
- # .. _`RFC 2616 section 5.1.2`: http://tools.ietf.org/html/rfc2616#section-5.1.2
+ # .. _`RFC 2616 section 5.1.2`: https://tools.ietf.org/html/rfc2616#section-5.1.2
if not path:
path = '/'
@@ -166,8 +166,8 @@ def normalize_base_string_uri(uri, host=None):
# to port 80 or when making an HTTPS request `RFC2818`_ to port 443.
# All other non-default port numbers MUST be included.
#
- # .. _`RFC2616`: http://tools.ietf.org/html/rfc2616
- # .. _`RFC2818`: http://tools.ietf.org/html/rfc2818
+ # .. _`RFC2616`: https://tools.ietf.org/html/rfc2616
+ # .. _`RFC2818`: https://tools.ietf.org/html/rfc2818
default_ports = (
('http', '80'),
('https', '443'),
@@ -190,7 +190,7 @@ def normalize_base_string_uri(uri, host=None):
# particular manner that is often different from their original
# encoding scheme, and concatenated into a single string.
#
-# .. _`section 3.4.1.3`: http://tools.ietf.org/html/rfc5849#section-3.4.1.3
+# .. _`section 3.4.1.3`: https://tools.ietf.org/html/rfc5849#section-3.4.1.3
def collect_parameters(uri_query='', body=[], headers=None,
exclude_oauth_signature=True, with_realm=False):
@@ -249,7 +249,7 @@ def collect_parameters(uri_query='', body=[], headers=None,
parameter instances (the "a3" parameter is used twice in this
request).
- .. _`section 3.4.1.3.1`: http://tools.ietf.org/html/rfc5849#section-3.4.1.3.1
+ .. _`section 3.4.1.3.1`: https://tools.ietf.org/html/rfc5849#section-3.4.1.3.1
"""
headers = headers or {}
params = []
@@ -264,8 +264,8 @@ def collect_parameters(uri_query='', body=[], headers=None,
# and values and decoding them as defined by
# `W3C.REC-html40-19980424`_, Section 17.13.4.
#
- # .. _`RFC3986, Section 3.4`: http://tools.ietf.org/html/rfc3986#section-3.4
- # .. _`W3C.REC-html40-19980424`: http://tools.ietf.org/html/rfc5849#ref-W3C.REC-html40-19980424
+ # .. _`RFC3986, Section 3.4`: https://tools.ietf.org/html/rfc3986#section-3.4
+ # .. _`W3C.REC-html40-19980424`: https://tools.ietf.org/html/rfc5849#ref-W3C.REC-html40-19980424
if uri_query:
params.extend(urldecode(uri_query))
@@ -274,7 +274,7 @@ def collect_parameters(uri_query='', body=[], headers=None,
# pairs excluding the "realm" parameter if present. The parameter
# values are decoded as defined by `Section 3.5.1`_.
#
- # .. _`Section 3.5.1`: http://tools.ietf.org/html/rfc5849#section-3.5.1
+ # .. _`Section 3.5.1`: https://tools.ietf.org/html/rfc5849#section-3.5.1
if headers:
headers_lower = dict((k.lower(), v) for k, v in headers.items())
authorization_header = headers_lower.get('authorization')
@@ -293,7 +293,7 @@ def collect_parameters(uri_query='', body=[], headers=None,
# * The HTTP request entity-header includes the "Content-Type"
# header field set to "application/x-www-form-urlencoded".
#
- # .._`W3C.REC-html40-19980424`: http://tools.ietf.org/html/rfc5849#ref-W3C.REC-html40-19980424
+ # .._`W3C.REC-html40-19980424`: https://tools.ietf.org/html/rfc5849#ref-W3C.REC-html40-19980424
# TODO: enforce header param inclusion conditions
bodyparams = extract_params(body) or []
@@ -383,18 +383,18 @@ def normalize_parameters(params):
dj82h48djs9d2&oauth_nonce=7d8f3e4a&oauth_signature_method=HMAC-SHA1
&oauth_timestamp=137131201&oauth_token=kkk9d7dh3k39sjv7
- .. _`section 3.4.1.3.2`: http://tools.ietf.org/html/rfc5849#section-3.4.1.3.2
+ .. _`section 3.4.1.3.2`: https://tools.ietf.org/html/rfc5849#section-3.4.1.3.2
"""
# The parameters collected in `Section 3.4.1.3`_ are normalized into a
# single string as follows:
#
- # .. _`Section 3.4.1.3`: http://tools.ietf.org/html/rfc5849#section-3.4.1.3
+ # .. _`Section 3.4.1.3`: https://tools.ietf.org/html/rfc5849#section-3.4.1.3
# 1. First, the name and value of each parameter are encoded
# (`Section 3.6`_).
#
- # .. _`Section 3.6`: http://tools.ietf.org/html/rfc5849#section-3.6
+ # .. _`Section 3.6`: https://tools.ietf.org/html/rfc5849#section-3.6
key_values = [(utils.escape(k), utils.escape(v)) for k, v in params]
# 2. The parameters are sorted by name, using ascending byte value
@@ -430,8 +430,8 @@ def sign_hmac_sha1(base_string, client_secret, resource_owner_secret):
Per `section 3.4.2`_ of the spec.
- .. _`RFC2104`: http://tools.ietf.org/html/rfc2104
- .. _`section 3.4.2`: http://tools.ietf.org/html/rfc5849#section-3.4.2
+ .. _`RFC2104`: https://tools.ietf.org/html/rfc2104
+ .. _`section 3.4.2`: https://tools.ietf.org/html/rfc5849#section-3.4.2
"""
# The HMAC-SHA1 function variables are used in following way:
@@ -439,13 +439,13 @@ def sign_hmac_sha1(base_string, client_secret, resource_owner_secret):
# text is set to the value of the signature base string from
# `Section 3.4.1.1`_.
#
- # .. _`Section 3.4.1.1`: http://tools.ietf.org/html/rfc5849#section-3.4.1.1
+ # .. _`Section 3.4.1.1`: https://tools.ietf.org/html/rfc5849#section-3.4.1.1
text = base_string
# key is set to the concatenated values of:
# 1. The client shared-secret, after being encoded (`Section 3.6`_).
#
- # .. _`Section 3.6`: http://tools.ietf.org/html/rfc5849#section-3.6
+ # .. _`Section 3.6`: https://tools.ietf.org/html/rfc5849#section-3.6
key = utils.escape(client_secret or '')
# 2. An "&" character (ASCII code 38), which MUST be included
@@ -454,7 +454,7 @@ def sign_hmac_sha1(base_string, client_secret, resource_owner_secret):
# 3. The token shared-secret, after being encoded (`Section 3.6`_).
#
- # .. _`Section 3.6`: http://tools.ietf.org/html/rfc5849#section-3.6
+ # .. _`Section 3.6`: https://tools.ietf.org/html/rfc5849#section-3.6
key += utils.escape(resource_owner_secret or '')
# FIXME: HMAC does not support unicode!
@@ -466,7 +466,7 @@ def sign_hmac_sha1(base_string, client_secret, resource_owner_secret):
# parameter, after the result octet string is base64-encoded
# per `RFC2045, Section 6.8`.
#
- # .. _`RFC2045, Section 6.8`: http://tools.ietf.org/html/rfc2045#section-6.8
+ # .. _`RFC2045, Section 6.8`: https://tools.ietf.org/html/rfc2045#section-6.8
return binascii.b2a_base64(signature.digest())[:-1].decode('utf-8')
@@ -487,8 +487,8 @@ def sign_hmac_sha256(base_string, client_secret, resource_owner_secret):
Per `section 3.4.2`_ of the spec.
- .. _`RFC4634`: http://tools.ietf.org/html/rfc4634
- .. _`section 3.4.2`: http://tools.ietf.org/html/rfc5849#section-3.4.2
+ .. _`RFC4634`: https://tools.ietf.org/html/rfc4634
+ .. _`section 3.4.2`: https://tools.ietf.org/html/rfc5849#section-3.4.2
"""
# The HMAC-SHA256 function variables are used in following way:
@@ -496,13 +496,13 @@ def sign_hmac_sha256(base_string, client_secret, resource_owner_secret):
# text is set to the value of the signature base string from
# `Section 3.4.1.1`_.
#
- # .. _`Section 3.4.1.1`: http://tools.ietf.org/html/rfc5849#section-3.4.1.1
+ # .. _`Section 3.4.1.1`: https://tools.ietf.org/html/rfc5849#section-3.4.1.1
text = base_string
# key is set to the concatenated values of:
# 1. The client shared-secret, after being encoded (`Section 3.6`_).
#
- # .. _`Section 3.6`: http://tools.ietf.org/html/rfc5849#section-3.6
+ # .. _`Section 3.6`: https://tools.ietf.org/html/rfc5849#section-3.6
key = utils.escape(client_secret or '')
# 2. An "&" character (ASCII code 38), which MUST be included
@@ -511,7 +511,7 @@ def sign_hmac_sha256(base_string, client_secret, resource_owner_secret):
# 3. The token shared-secret, after being encoded (`Section 3.6`_).
#
- # .. _`Section 3.6`: http://tools.ietf.org/html/rfc5849#section-3.6
+ # .. _`Section 3.6`: https://tools.ietf.org/html/rfc5849#section-3.6
key += utils.escape(resource_owner_secret or '')
# FIXME: HMAC does not support unicode!
@@ -523,7 +523,7 @@ def sign_hmac_sha256(base_string, client_secret, resource_owner_secret):
# parameter, after the result octet string is base64-encoded
# per `RFC2045, Section 6.8`.
#
- # .. _`RFC2045, Section 6.8`: http://tools.ietf.org/html/rfc2045#section-6.8
+ # .. _`RFC2045, Section 6.8`: https://tools.ietf.org/html/rfc2045#section-6.8
return binascii.b2a_base64(signature.digest())[:-1].decode('utf-8')
_jwtrs1 = None
@@ -548,8 +548,8 @@ def sign_rsa_sha1(base_string, rsa_private_key):
with the server that included its RSA public key (in a manner that is
beyond the scope of this specification).
- .. _`section 3.4.3`: http://tools.ietf.org/html/rfc5849#section-3.4.3
- .. _`RFC3447, Section 8.2`: http://tools.ietf.org/html/rfc3447#section-8.2
+ .. _`section 3.4.3`: https://tools.ietf.org/html/rfc5849#section-3.4.3
+ .. _`RFC3447, Section 8.2`: https://tools.ietf.org/html/rfc3447#section-8.2
"""
if isinstance(base_string, unicode_type):
@@ -578,7 +578,7 @@ def sign_plaintext(client_secret, resource_owner_secret):
utilize the signature base string or the "oauth_timestamp" and
"oauth_nonce" parameters.
- .. _`section 3.4.4`: http://tools.ietf.org/html/rfc5849#section-3.4.4
+ .. _`section 3.4.4`: https://tools.ietf.org/html/rfc5849#section-3.4.4
"""
@@ -587,7 +587,7 @@ def sign_plaintext(client_secret, resource_owner_secret):
# 1. The client shared-secret, after being encoded (`Section 3.6`_).
#
- # .. _`Section 3.6`: http://tools.ietf.org/html/rfc5849#section-3.6
+ # .. _`Section 3.6`: https://tools.ietf.org/html/rfc5849#section-3.6
signature = utils.escape(client_secret or '')
# 2. An "&" character (ASCII code 38), which MUST be included even
@@ -596,7 +596,7 @@ def sign_plaintext(client_secret, resource_owner_secret):
# 3. The token shared-secret, after being encoded (`Section 3.6`_).
#
- # .. _`Section 3.6`: http://tools.ietf.org/html/rfc5849#section-3.6
+ # .. _`Section 3.6`: https://tools.ietf.org/html/rfc5849#section-3.6
signature += utils.escape(resource_owner_secret or '')
return signature
@@ -612,7 +612,7 @@ def verify_hmac_sha1(request, client_secret=None,
Per `section 3.4`_ of the spec.
- .. _`section 3.4`: http://tools.ietf.org/html/rfc5849#section-3.4
+ .. _`section 3.4`: https://tools.ietf.org/html/rfc5849#section-3.4
To satisfy `RFC2616 section 5.2`_ item 1, the request argument's uri
attribute MUST be an absolute URI whose netloc part identifies the
@@ -620,7 +620,7 @@ def verify_hmac_sha1(request, client_secret=None,
item of the request argument's headers dict attribute will be
ignored.
- .. _`RFC2616 section 5.2`: http://tools.ietf.org/html/rfc2616#section-5.2
+ .. _`RFC2616 section 5.2`: https://tools.ietf.org/html/rfc2616#section-5.2
"""
norm_params = normalize_parameters(request.params)
@@ -646,7 +646,7 @@ def verify_rsa_sha1(request, rsa_public_key):
Note this method requires the jwt and cryptography libraries.
- .. _`section 3.4.3`: http://tools.ietf.org/html/rfc5849#section-3.4.3
+ .. _`section 3.4.3`: https://tools.ietf.org/html/rfc5849#section-3.4.3
To satisfy `RFC2616 section 5.2`_ item 1, the request argument's uri
attribute MUST be an absolute URI whose netloc part identifies the
@@ -654,7 +654,7 @@ def verify_rsa_sha1(request, rsa_public_key):
item of the request argument's headers dict attribute will be
ignored.
- .. _`RFC2616 section 5.2`: http://tools.ietf.org/html/rfc2616#section-5.2
+ .. _`RFC2616 section 5.2`: https://tools.ietf.org/html/rfc2616#section-5.2
"""
norm_params = normalize_parameters(request.params)
uri = normalize_base_string_uri(request.uri)
@@ -675,7 +675,7 @@ def verify_plaintext(request, client_secret=None, resource_owner_secret=None):
Per `section 3.4`_ of the spec.
- .. _`section 3.4`: http://tools.ietf.org/html/rfc5849#section-3.4
+ .. _`section 3.4`: https://tools.ietf.org/html/rfc5849#section-3.4
"""
signature = sign_plaintext(client_secret, resource_owner_secret)
match = safe_string_equals(signature, request.signature)
diff --git a/oauthlib/oauth1/rfc5849/utils.py b/oauthlib/oauth1/rfc5849/utils.py
index 979e5f6..3762e3b 100644
--- a/oauthlib/oauth1/rfc5849/utils.py
+++ b/oauthlib/oauth1/rfc5849/utils.py
@@ -49,7 +49,7 @@ def escape(u):
Per `section 3.6`_ of the spec.
- .. _`section 3.6`: http://tools.ietf.org/html/rfc5849#section-3.6
+ .. _`section 3.6`: https://tools.ietf.org/html/rfc5849#section-3.6
"""
if not isinstance(u, unicode_type):
diff --git a/oauthlib/oauth2/rfc6749/clients/backend_application.py b/oauthlib/oauth2/rfc6749/clients/backend_application.py
index 7505b0d..cbad8b7 100644
--- a/oauthlib/oauth2/rfc6749/clients/backend_application.py
+++ b/oauthlib/oauth2/rfc6749/clients/backend_application.py
@@ -52,9 +52,9 @@ class BackendApplicationClient(Client):
>>> client.prepare_request_body(scope=['hello', 'world'])
'grant_type=client_credentials&scope=hello+world'
- .. _`Appendix B`: http://tools.ietf.org/html/rfc6749#appendix-B
- .. _`Section 3.3`: http://tools.ietf.org/html/rfc6749#section-3.3
- .. _`Section 3.2.1`: http://tools.ietf.org/html/rfc6749#section-3.2.1
+ .. _`Appendix B`: https://tools.ietf.org/html/rfc6749#appendix-B
+ .. _`Section 3.3`: https://tools.ietf.org/html/rfc6749#section-3.3
+ .. _`Section 3.2.1`: https://tools.ietf.org/html/rfc6749#section-3.2.1
"""
return prepare_token_request('client_credentials', body=body,
scope=scope, **kwargs)
diff --git a/oauthlib/oauth2/rfc6749/clients/base.py b/oauthlib/oauth2/rfc6749/clients/base.py
index 5c5acee..406832d 100644
--- a/oauthlib/oauth2/rfc6749/clients/base.py
+++ b/oauthlib/oauth2/rfc6749/clients/base.py
@@ -9,6 +9,7 @@ for consuming OAuth 2.0 RFC6749.
from __future__ import absolute_import, unicode_literals
import time
+import warnings
from oauthlib.common import generate_token
from oauthlib.oauth2.rfc6749 import tokens
@@ -111,8 +112,10 @@ class Client(object):
self.state_generator = state_generator
self.state = state
self.redirect_url = redirect_url
+ self.code = None
+ self.expires_in = None
self._expires_at = None
- self._populate_attributes(self.token)
+ self.populate_token_attributes(self.token)
@property
def token_types(self):
@@ -140,6 +143,7 @@ class Client(object):
def parse_request_uri_response(self, *args, **kwargs):
"""Abstract method used to parse redirection responses."""
+ raise NotImplementedError("Must be implemented by inheriting classes.")
def add_token(self, uri, http_method='GET', body=None, headers=None,
token_placement=None, **kwargs):
@@ -173,8 +177,8 @@ class Client(object):
nonce="274312:dj83hs9s",
mac="kDZvddkndxvhGRXZhvuDjEWhGeE="
- .. _`I-D.ietf-oauth-v2-bearer`: http://tools.ietf.org/html/rfc6749#section-12.2
- .. _`I-D.ietf-oauth-v2-http-mac`: http://tools.ietf.org/html/rfc6749#section-12.2
+ .. _`I-D.ietf-oauth-v2-bearer`: https://tools.ietf.org/html/rfc6749#section-12.2
+ .. _`I-D.ietf-oauth-v2-http-mac`: https://tools.ietf.org/html/rfc6749#section-12.2
"""
if not is_secure_transport(uri):
raise InsecureTransportError()
@@ -401,12 +405,12 @@ class Client(object):
Providers may supply this in all responses but are required to only
if it has changed since the authorization request.
- .. _`Section 5.1`: http://tools.ietf.org/html/rfc6749#section-5.1
- .. _`Section 5.2`: http://tools.ietf.org/html/rfc6749#section-5.2
- .. _`Section 7.1`: http://tools.ietf.org/html/rfc6749#section-7.1
+ .. _`Section 5.1`: https://tools.ietf.org/html/rfc6749#section-5.1
+ .. _`Section 5.2`: https://tools.ietf.org/html/rfc6749#section-5.2
+ .. _`Section 7.1`: https://tools.ietf.org/html/rfc6749#section-7.1
"""
self.token = parse_token_response(body, scope=scope)
- self._populate_attributes(self.token)
+ self.populate_token_attributes(self.token)
return self.token
def prepare_refresh_body(self, body='', refresh_token=None, scope=None, **kwargs):
@@ -460,7 +464,18 @@ class Client(object):
return uri, headers, body
def _populate_attributes(self, response):
- """Add commonly used values such as access_token to self."""
+ warnings.warn("Please switch to the public method "
+ "populate_token_attributes.", DeprecationWarning)
+ return self.populate_token_attributes(response)
+
+ def populate_code_attributes(self, response):
+ """Add attributes from an auth code response to self."""
+
+ if 'code' in response:
+ self.code = response.get('code')
+
+ def populate_token_attributes(self, response):
+ """Add attributes from a token exchange response to self."""
if 'access_token' in response:
self.access_token = response.get('access_token')
@@ -478,9 +493,6 @@ class Client(object):
if 'expires_at' in response:
self._expires_at = int(response.get('expires_at'))
- if 'code' in response:
- self.code = response.get('code')
-
if 'mac_key' in response:
self.mac_key = response.get('mac_key')
diff --git a/oauthlib/oauth2/rfc6749/clients/legacy_application.py b/oauthlib/oauth2/rfc6749/clients/legacy_application.py
index 57fe99e..b16fc9f 100644
--- a/oauthlib/oauth2/rfc6749/clients/legacy_application.py
+++ b/oauthlib/oauth2/rfc6749/clients/legacy_application.py
@@ -64,9 +64,9 @@ class LegacyApplicationClient(Client):
>>> client.prepare_request_body(username='foo', password='bar', scope=['hello', 'world'])
'grant_type=password&username=foo&scope=hello+world&password=bar'
- .. _`Appendix B`: http://tools.ietf.org/html/rfc6749#appendix-B
- .. _`Section 3.3`: http://tools.ietf.org/html/rfc6749#section-3.3
- .. _`Section 3.2.1`: http://tools.ietf.org/html/rfc6749#section-3.2.1
+ .. _`Appendix B`: https://tools.ietf.org/html/rfc6749#appendix-B
+ .. _`Section 3.3`: https://tools.ietf.org/html/rfc6749#section-3.3
+ .. _`Section 3.2.1`: https://tools.ietf.org/html/rfc6749#section-3.2.1
"""
return prepare_token_request('password', body=body, username=username,
password=password, scope=scope, **kwargs)
diff --git a/oauthlib/oauth2/rfc6749/clients/mobile_application.py b/oauthlib/oauth2/rfc6749/clients/mobile_application.py
index 490efcd..aa20daa 100644
--- a/oauthlib/oauth2/rfc6749/clients/mobile_application.py
+++ b/oauthlib/oauth2/rfc6749/clients/mobile_application.py
@@ -85,11 +85,11 @@ class MobileApplicationClient(Client):
>>> client.prepare_request_uri('https://example.com', foo='bar')
'https://example.com?client_id=your_id&response_type=token&foo=bar'
- .. _`Appendix B`: http://tools.ietf.org/html/rfc6749#appendix-B
- .. _`Section 2.2`: http://tools.ietf.org/html/rfc6749#section-2.2
- .. _`Section 3.1.2`: http://tools.ietf.org/html/rfc6749#section-3.1.2
- .. _`Section 3.3`: http://tools.ietf.org/html/rfc6749#section-3.3
- .. _`Section 10.12`: http://tools.ietf.org/html/rfc6749#section-10.12
+ .. _`Appendix B`: https://tools.ietf.org/html/rfc6749#appendix-B
+ .. _`Section 2.2`: https://tools.ietf.org/html/rfc6749#section-2.2
+ .. _`Section 3.1.2`: https://tools.ietf.org/html/rfc6749#section-3.1.2
+ .. _`Section 3.3`: https://tools.ietf.org/html/rfc6749#section-3.3
+ .. _`Section 10.12`: https://tools.ietf.org/html/rfc6749#section-10.12
"""
return prepare_grant_uri(uri, self.client_id, 'token',
redirect_uri=redirect_uri, state=state, scope=scope, **kwargs)
@@ -164,9 +164,9 @@ class MobileApplicationClient(Client):
>>> client.parse_request_body_response(response_body, scope=['other'])
('Scope has changed from "other" to "hello world".', ['other'], ['hello', 'world'])
- .. _`Section 7.1`: http://tools.ietf.org/html/rfc6749#section-7.1
- .. _`Section 3.3`: http://tools.ietf.org/html/rfc6749#section-3.3
+ .. _`Section 7.1`: https://tools.ietf.org/html/rfc6749#section-7.1
+ .. _`Section 3.3`: https://tools.ietf.org/html/rfc6749#section-3.3
"""
self.token = parse_implicit_response(uri, state=state, scope=scope)
- self._populate_attributes(self.token)
+ self.populate_token_attributes(self.token)
return self.token
diff --git a/oauthlib/oauth2/rfc6749/clients/service_application.py b/oauthlib/oauth2/rfc6749/clients/service_application.py
index e6c3270..7f336bb 100644
--- a/oauthlib/oauth2/rfc6749/clients/service_application.py
+++ b/oauthlib/oauth2/rfc6749/clients/service_application.py
@@ -136,7 +136,7 @@ class ServiceApplicationClient(Client):
eyJpc3Mi[...omitted for brevity...].
J9l-ZhwP[...omitted for brevity...]
- .. _`Section 3.2.1`: http://tools.ietf.org/html/rfc6749#section-3.2.1
+ .. _`Section 3.2.1`: https://tools.ietf.org/html/rfc6749#section-3.2.1
"""
import jwt
@@ -146,8 +146,8 @@ class ServiceApplicationClient(Client):
' token requests.')
claim = {
'iss': issuer or self.issuer,
- 'aud': audience or self.issuer,
- 'sub': subject or self.issuer,
+ 'aud': audience or self.audience,
+ 'sub': subject or self.subject,
'exp': int(expires_at or time.time() + 3600),
'iat': int(issued_at or time.time()),
}
diff --git a/oauthlib/oauth2/rfc6749/clients/web_application.py b/oauthlib/oauth2/rfc6749/clients/web_application.py
index c099d99..c14a5f8 100644
--- a/oauthlib/oauth2/rfc6749/clients/web_application.py
+++ b/oauthlib/oauth2/rfc6749/clients/web_application.py
@@ -76,11 +76,11 @@ class WebApplicationClient(Client):
>>> client.prepare_request_uri('https://example.com', foo='bar')
'https://example.com?client_id=your_id&response_type=code&foo=bar'
- .. _`Appendix B`: http://tools.ietf.org/html/rfc6749#appendix-B
- .. _`Section 2.2`: http://tools.ietf.org/html/rfc6749#section-2.2
- .. _`Section 3.1.2`: http://tools.ietf.org/html/rfc6749#section-3.1.2
- .. _`Section 3.3`: http://tools.ietf.org/html/rfc6749#section-3.3
- .. _`Section 10.12`: http://tools.ietf.org/html/rfc6749#section-10.12
+ .. _`Appendix B`: https://tools.ietf.org/html/rfc6749#appendix-B
+ .. _`Section 2.2`: https://tools.ietf.org/html/rfc6749#section-2.2
+ .. _`Section 3.1.2`: https://tools.ietf.org/html/rfc6749#section-3.1.2
+ .. _`Section 3.3`: https://tools.ietf.org/html/rfc6749#section-3.3
+ .. _`Section 10.12`: https://tools.ietf.org/html/rfc6749#section-10.12
"""
return prepare_grant_uri(uri, self.client_id, 'code',
redirect_uri=redirect_uri, scope=scope, state=state, **kwargs)
@@ -120,12 +120,12 @@ class WebApplicationClient(Client):
>>> client.prepare_request_body(code='sh35ksdf09sf', foo='bar')
'grant_type=authorization_code&code=sh35ksdf09sf&foo=bar'
- .. _`Section 4.1.1`: http://tools.ietf.org/html/rfc6749#section-4.1.1
- .. _`Section 3.2.1`: http://tools.ietf.org/html/rfc6749#section-3.2.1
+ .. _`Section 4.1.1`: https://tools.ietf.org/html/rfc6749#section-4.1.1
+ .. _`Section 3.2.1`: https://tools.ietf.org/html/rfc6749#section-3.2.1
"""
code = code or self.code
return prepare_token_request('authorization_code', code=code, body=body,
- client_id=self.client_id, redirect_uri=redirect_uri, **kwargs)
+ client_id=client_id, redirect_uri=redirect_uri, **kwargs)
def parse_request_uri_response(self, uri, state=None):
"""Parse the URI query for code and state.
@@ -172,5 +172,5 @@ class WebApplicationClient(Client):
oauthlib.oauth2.rfc6749.errors.MismatchingStateError
"""
response = parse_authorization_code_response(uri, state=state)
- self._populate_attributes(response)
+ self.populate_code_attributes(response)
return response
diff --git a/oauthlib/oauth2/rfc6749/endpoints/authorization.py b/oauthlib/oauth2/rfc6749/endpoints/authorization.py
index b6e0734..92cde34 100644
--- a/oauthlib/oauth2/rfc6749/endpoints/authorization.py
+++ b/oauthlib/oauth2/rfc6749/endpoints/authorization.py
@@ -59,7 +59,7 @@ class AuthorizationEndpoint(BaseEndpoint):
# Enforced through the design of oauthlib.common.Request
- .. _`Appendix B`: http://tools.ietf.org/html/rfc6749#appendix-B
+ .. _`Appendix B`: https://tools.ietf.org/html/rfc6749#appendix-B
"""
def __init__(self, default_response_type, default_token_type,
diff --git a/oauthlib/oauth2/rfc6749/endpoints/pre_configured.py b/oauthlib/oauth2/rfc6749/endpoints/pre_configured.py
index 378339a..66af516 100644
--- a/oauthlib/oauth2/rfc6749/endpoints/pre_configured.py
+++ b/oauthlib/oauth2/rfc6749/endpoints/pre_configured.py
@@ -16,7 +16,7 @@ from ..grant_types import (AuthCodeGrantDispatcher, AuthorizationCodeGrant,
OpenIDConnectHybrid,
RefreshTokenGrant,
ResourceOwnerPasswordCredentialsGrant)
-from ..tokens import BearerToken
+from ..tokens import BearerToken, JWTToken
from .authorization import AuthorizationEndpoint
from .introspect import IntrospectEndpoint
from .resource import ResourceEndpoint
@@ -58,6 +58,9 @@ class Server(AuthorizationEndpoint, IntrospectEndpoint, TokenEndpoint,
bearer = BearerToken(request_validator, token_generator,
token_expires_in, refresh_token_generator)
+ jwt = JWTToken(request_validator, token_generator,
+ token_expires_in, refresh_token_generator)
+
auth_grant_choice = AuthCodeGrantDispatcher(default_auth_grant=auth_grant, oidc_auth_grant=openid_connect_auth)
implicit_grant_choice = ImplicitTokenGrantDispatcher(default_implicit_grant=implicit_grant, oidc_implicit_grant=openid_connect_implicit)
@@ -87,7 +90,7 @@ class Server(AuthorizationEndpoint, IntrospectEndpoint, TokenEndpoint,
},
default_token_type=bearer)
ResourceEndpoint.__init__(self, default_token='Bearer',
- token_types={'Bearer': bearer})
+ token_types={'Bearer': bearer, 'JWT': jwt})
RevocationEndpoint.__init__(self, request_validator)
IntrospectEndpoint.__init__(self, request_validator)
diff --git a/oauthlib/oauth2/rfc6749/endpoints/resource.py b/oauthlib/oauth2/rfc6749/endpoints/resource.py
index d03ed21..f19c60c 100644
--- a/oauthlib/oauth2/rfc6749/endpoints/resource.py
+++ b/oauthlib/oauth2/rfc6749/endpoints/resource.py
@@ -83,5 +83,5 @@ class ResourceEndpoint(BaseEndpoint):
to give an estimation based on the request.
"""
estimates = sorted(((t.estimate_type(request), n)
- for n, t in self.tokens.items()))
+ for n, t in self.tokens.items()), reverse=True)
return estimates[0][1] if len(estimates) else None
diff --git a/oauthlib/oauth2/rfc6749/endpoints/revocation.py b/oauthlib/oauth2/rfc6749/endpoints/revocation.py
index 4364b81..d5b5b78 100644
--- a/oauthlib/oauth2/rfc6749/endpoints/revocation.py
+++ b/oauthlib/oauth2/rfc6749/endpoints/revocation.py
@@ -5,7 +5,7 @@ oauthlib.oauth2.rfc6749.endpoint.revocation
An implementation of the OAuth 2 `Token Revocation`_ spec (draft 11).
-.. _`Token Revocation`: http://tools.ietf.org/html/draft-ietf-oauth-revocation-11
+.. _`Token Revocation`: https://tools.ietf.org/html/draft-ietf-oauth-revocation-11
"""
from __future__ import absolute_import, unicode_literals
@@ -110,11 +110,11 @@ class RevocationEndpoint(BaseEndpoint):
The client also includes its authentication credentials as described in
`Section 2.3`_. of [`RFC6749`_].
- .. _`section 1.4`: http://tools.ietf.org/html/rfc6749#section-1.4
- .. _`section 1.5`: http://tools.ietf.org/html/rfc6749#section-1.5
- .. _`section 2.3`: http://tools.ietf.org/html/rfc6749#section-2.3
- .. _`Section 4.1.2`: http://tools.ietf.org/html/draft-ietf-oauth-revocation-11#section-4.1.2
- .. _`RFC6749`: http://tools.ietf.org/html/rfc6749
+ .. _`section 1.4`: https://tools.ietf.org/html/rfc6749#section-1.4
+ .. _`section 1.5`: https://tools.ietf.org/html/rfc6749#section-1.5
+ .. _`section 2.3`: https://tools.ietf.org/html/rfc6749#section-2.3
+ .. _`Section 4.1.2`: https://tools.ietf.org/html/draft-ietf-oauth-revocation-11#section-4.1.2
+ .. _`RFC6749`: https://tools.ietf.org/html/rfc6749
"""
if not request.token:
raise InvalidRequestError(request=request,
diff --git a/oauthlib/oauth2/rfc6749/endpoints/token.py b/oauthlib/oauth2/rfc6749/endpoints/token.py
index ece6325..90fb16f 100644
--- a/oauthlib/oauth2/rfc6749/endpoints/token.py
+++ b/oauthlib/oauth2/rfc6749/endpoints/token.py
@@ -59,7 +59,7 @@ class TokenEndpoint(BaseEndpoint):
# Delegated to each grant type.
- .. _`Appendix B`: http://tools.ietf.org/html/rfc6749#appendix-B
+ .. _`Appendix B`: https://tools.ietf.org/html/rfc6749#appendix-B
"""
def __init__(self, default_grant_type, default_token_type, grant_types):
diff --git a/oauthlib/oauth2/rfc6749/grant_types/authorization_code.py b/oauthlib/oauth2/rfc6749/grant_types/authorization_code.py
index 8661c35..0660263 100644
--- a/oauthlib/oauth2/rfc6749/grant_types/authorization_code.py
+++ b/oauthlib/oauth2/rfc6749/grant_types/authorization_code.py
@@ -91,7 +91,7 @@ class AuthorizationCodeGrant(GrantTypeBase):
step (C). If valid, the authorization server responds back with
an access token and, optionally, a refresh token.
- .. _`Authorization Code Grant`: http://tools.ietf.org/html/rfc6749#section-4.1
+ .. _`Authorization Code Grant`: https://tools.ietf.org/html/rfc6749#section-4.1
"""
default_response_mode = 'query'
@@ -175,11 +175,11 @@ class AuthorizationCodeGrant(GrantTypeBase):
File "oauthlib/oauth2/rfc6749/grant_types.py", line 591, in validate_authorization_request
oauthlib.oauth2.rfc6749.errors.InvalidClientIdError
- .. _`Appendix B`: http://tools.ietf.org/html/rfc6749#appendix-B
- .. _`Section 2.2`: http://tools.ietf.org/html/rfc6749#section-2.2
- .. _`Section 3.1.2`: http://tools.ietf.org/html/rfc6749#section-3.1.2
- .. _`Section 3.3`: http://tools.ietf.org/html/rfc6749#section-3.3
- .. _`Section 10.12`: http://tools.ietf.org/html/rfc6749#section-10.12
+ .. _`Appendix B`: https://tools.ietf.org/html/rfc6749#appendix-B
+ .. _`Section 2.2`: https://tools.ietf.org/html/rfc6749#section-2.2
+ .. _`Section 3.1.2`: https://tools.ietf.org/html/rfc6749#section-3.1.2
+ .. _`Section 3.3`: https://tools.ietf.org/html/rfc6749#section-3.3
+ .. _`Section 10.12`: https://tools.ietf.org/html/rfc6749#section-10.12
"""
try:
# request.scopes is only mandated in post auth and both pre and
@@ -206,7 +206,7 @@ class AuthorizationCodeGrant(GrantTypeBase):
# the authorization server informs the client by adding the following
# parameters to the query component of the redirection URI using the
# "application/x-www-form-urlencoded" format, per Appendix B:
- # http://tools.ietf.org/html/rfc6749#appendix-B
+ # https://tools.ietf.org/html/rfc6749#appendix-B
except errors.OAuth2Error as e:
log.debug('Client error during validation of %r. %r.', request, e)
request.redirect_uri = request.redirect_uri or self.error_uri
@@ -285,7 +285,7 @@ class AuthorizationCodeGrant(GrantTypeBase):
raise errors.InvalidRequestFatalError(description='Duplicate %s parameter.' % param, request=request)
# REQUIRED. The client identifier as described in Section 2.2.
- # http://tools.ietf.org/html/rfc6749#section-2.2
+ # https://tools.ietf.org/html/rfc6749#section-2.2
if not request.client_id:
raise errors.MissingClientIdError(request=request)
@@ -293,7 +293,7 @@ class AuthorizationCodeGrant(GrantTypeBase):
raise errors.InvalidClientIdError(request=request)
# OPTIONAL. As described in Section 3.1.2.
- # http://tools.ietf.org/html/rfc6749#section-3.1.2
+ # https://tools.ietf.org/html/rfc6749#section-3.1.2
log.debug('Validating redirection uri %s for client %s.',
request.redirect_uri, request.client_id)
if request.redirect_uri is not None:
@@ -320,7 +320,7 @@ class AuthorizationCodeGrant(GrantTypeBase):
# the authorization server informs the client by adding the following
# parameters to the query component of the redirection URI using the
# "application/x-www-form-urlencoded" format, per Appendix B.
- # http://tools.ietf.org/html/rfc6749#appendix-B
+ # https://tools.ietf.org/html/rfc6749#appendix-B
# Note that the correct parameters to be added are automatically
# populated through the use of specific exceptions.
@@ -346,7 +346,7 @@ class AuthorizationCodeGrant(GrantTypeBase):
raise errors.UnauthorizedClientError(request=request)
# OPTIONAL. The scope of the access request as described by Section 3.3
- # http://tools.ietf.org/html/rfc6749#section-3.3
+ # https://tools.ietf.org/html/rfc6749#section-3.3
self.validate_scopes(request)
request_info.update({
@@ -384,14 +384,14 @@ class AuthorizationCodeGrant(GrantTypeBase):
# credentials (or assigned other authentication requirements), the
# client MUST authenticate with the authorization server as described
# in Section 3.2.1.
- # http://tools.ietf.org/html/rfc6749#section-3.2.1
+ # https://tools.ietf.org/html/rfc6749#section-3.2.1
if not self.request_validator.authenticate_client(request):
log.debug('Client authentication failed, %r.', request)
raise errors.InvalidClientError(request=request)
elif not self.request_validator.authenticate_client_id(request.client_id, request):
# REQUIRED, if the client is not authenticating with the
# authorization server as described in Section 3.2.1.
- # http://tools.ietf.org/html/rfc6749#section-3.2.1
+ # https://tools.ietf.org/html/rfc6749#section-3.2.1
log.debug('Client authentication failed, %r.', request)
raise errors.InvalidClientError(request=request)
@@ -421,7 +421,8 @@ class AuthorizationCodeGrant(GrantTypeBase):
# authorization request as described in Section 4.1.1, and their
# values MUST be identical.
if not self.request_validator.confirm_redirect_uri(request.client_id, request.code,
- request.redirect_uri, request.client):
+ request.redirect_uri, request.client,
+ request):
log.debug('Redirect_uri (%r) invalid for client %r (%r).',
request.redirect_uri, request.client_id, request.client)
raise errors.MismatchingRedirectURIError(request=request)
diff --git a/oauthlib/oauth2/rfc6749/grant_types/client_credentials.py b/oauthlib/oauth2/rfc6749/grant_types/client_credentials.py
index bf6c87f..4c50a78 100644
--- a/oauthlib/oauth2/rfc6749/grant_types/client_credentials.py
+++ b/oauthlib/oauth2/rfc6749/grant_types/client_credentials.py
@@ -47,7 +47,7 @@ class ClientCredentialsGrant(GrantTypeBase):
(B) The authorization server authenticates the client, and if valid,
issues an access token.
- .. _`Client Credentials Grant`: http://tools.ietf.org/html/rfc6749#section-4.4
+ .. _`Client Credentials Grant`: https://tools.ietf.org/html/rfc6749#section-4.4
"""
def create_token_response(self, request, token_handler):
@@ -59,8 +59,8 @@ class ClientCredentialsGrant(GrantTypeBase):
failed client authentication or is invalid, the authorization server
returns an error response as described in `Section 5.2`_.
- .. _`Section 5.1`: http://tools.ietf.org/html/rfc6749#section-5.1
- .. _`Section 5.2`: http://tools.ietf.org/html/rfc6749#section-5.2
+ .. _`Section 5.1`: https://tools.ietf.org/html/rfc6749#section-5.1
+ .. _`Section 5.2`: https://tools.ietf.org/html/rfc6749#section-5.2
"""
headers = {
'Content-Type': 'application/json',
diff --git a/oauthlib/oauth2/rfc6749/grant_types/implicit.py b/oauthlib/oauth2/rfc6749/grant_types/implicit.py
index 2b9c49d..bdab814 100644
--- a/oauthlib/oauth2/rfc6749/grant_types/implicit.py
+++ b/oauthlib/oauth2/rfc6749/grant_types/implicit.py
@@ -111,9 +111,9 @@ class ImplicitGrant(GrantTypeBase):
See `Section 10.3`_ and `Section 10.16`_ for important security considerations
when using the implicit grant.
- .. _`Implicit Grant`: http://tools.ietf.org/html/rfc6749#section-4.2
- .. _`Section 10.3`: http://tools.ietf.org/html/rfc6749#section-10.3
- .. _`Section 10.16`: http://tools.ietf.org/html/rfc6749#section-10.16
+ .. _`Implicit Grant`: https://tools.ietf.org/html/rfc6749#section-4.2
+ .. _`Section 10.3`: https://tools.ietf.org/html/rfc6749#section-10.3
+ .. _`Section 10.16`: https://tools.ietf.org/html/rfc6749#section-10.16
"""
response_types = ['token']
@@ -152,11 +152,11 @@ class ImplicitGrant(GrantTypeBase):
access token matches a redirection URI registered by the client as
described in `Section 3.1.2`_.
- .. _`Section 2.2`: http://tools.ietf.org/html/rfc6749#section-2.2
- .. _`Section 3.1.2`: http://tools.ietf.org/html/rfc6749#section-3.1.2
- .. _`Section 3.3`: http://tools.ietf.org/html/rfc6749#section-3.3
- .. _`Section 10.12`: http://tools.ietf.org/html/rfc6749#section-10.12
- .. _`Appendix B`: http://tools.ietf.org/html/rfc6749#appendix-B
+ .. _`Section 2.2`: https://tools.ietf.org/html/rfc6749#section-2.2
+ .. _`Section 3.1.2`: https://tools.ietf.org/html/rfc6749#section-3.1.2
+ .. _`Section 3.3`: https://tools.ietf.org/html/rfc6749#section-3.3
+ .. _`Section 10.12`: https://tools.ietf.org/html/rfc6749#section-10.12
+ .. _`Appendix B`: https://tools.ietf.org/html/rfc6749#appendix-B
"""
return self.create_token_response(request, token_handler)
@@ -195,9 +195,9 @@ class ImplicitGrant(GrantTypeBase):
The authorization server MUST NOT issue a refresh token.
- .. _`Appendix B`: http://tools.ietf.org/html/rfc6749#appendix-B
- .. _`Section 3.3`: http://tools.ietf.org/html/rfc6749#section-3.3
- .. _`Section 7.1`: http://tools.ietf.org/html/rfc6749#section-7.1
+ .. _`Appendix B`: https://tools.ietf.org/html/rfc6749#appendix-B
+ .. _`Section 3.3`: https://tools.ietf.org/html/rfc6749#section-3.3
+ .. _`Section 7.1`: https://tools.ietf.org/html/rfc6749#section-7.1
"""
try:
# request.scopes is only mandated in post auth and both pre and
@@ -222,7 +222,7 @@ class ImplicitGrant(GrantTypeBase):
# the authorization server informs the client by adding the following
# parameters to the fragment component of the redirection URI using the
# "application/x-www-form-urlencoded" format, per Appendix B:
- # http://tools.ietf.org/html/rfc6749#appendix-B
+ # https://tools.ietf.org/html/rfc6749#appendix-B
except errors.OAuth2Error as e:
log.debug('Client error during validation of %r. %r.', request, e)
return {'Location': common.add_params_to_uri(request.redirect_uri, e.twotuples,
@@ -285,7 +285,7 @@ class ImplicitGrant(GrantTypeBase):
raise errors.InvalidRequestFatalError(description='Duplicate %s parameter.' % param, request=request)
# REQUIRED. The client identifier as described in Section 2.2.
- # http://tools.ietf.org/html/rfc6749#section-2.2
+ # https://tools.ietf.org/html/rfc6749#section-2.2
if not request.client_id:
raise errors.MissingClientIdError(request=request)
@@ -293,7 +293,7 @@ class ImplicitGrant(GrantTypeBase):
raise errors.InvalidClientIdError(request=request)
# OPTIONAL. As described in Section 3.1.2.
- # http://tools.ietf.org/html/rfc6749#section-3.1.2
+ # https://tools.ietf.org/html/rfc6749#section-3.1.2
if request.redirect_uri is not None:
request.using_default_redirect_uri = False
log.debug('Using provided redirect_uri %s', request.redirect_uri)
@@ -304,7 +304,7 @@ class ImplicitGrant(GrantTypeBase):
# to which it will redirect the access token matches a
# redirection URI registered by the client as described in
# Section 3.1.2.
- # http://tools.ietf.org/html/rfc6749#section-3.1.2
+ # https://tools.ietf.org/html/rfc6749#section-3.1.2
if not self.request_validator.validate_redirect_uri(
request.client_id, request.redirect_uri, request):
raise errors.MismatchingRedirectURIError(request=request)
@@ -328,7 +328,7 @@ class ImplicitGrant(GrantTypeBase):
# the authorization server informs the client by adding the following
# parameters to the fragment component of the redirection URI using the
# "application/x-www-form-urlencoded" format, per Appendix B.
- # http://tools.ietf.org/html/rfc6749#appendix-B
+ # https://tools.ietf.org/html/rfc6749#appendix-B
# Note that the correct parameters to be added are automatically
# populated through the use of specific exceptions
@@ -351,7 +351,7 @@ class ImplicitGrant(GrantTypeBase):
raise errors.UnauthorizedClientError(request=request)
# OPTIONAL. The scope of the access request as described by Section 3.3
- # http://tools.ietf.org/html/rfc6749#section-3.3
+ # https://tools.ietf.org/html/rfc6749#section-3.3
self.validate_scopes(request)
request_info.update({
diff --git a/oauthlib/oauth2/rfc6749/grant_types/refresh_token.py b/oauthlib/oauth2/rfc6749/grant_types/refresh_token.py
index 6233e7c..c2d86f7 100644
--- a/oauthlib/oauth2/rfc6749/grant_types/refresh_token.py
+++ b/oauthlib/oauth2/rfc6749/grant_types/refresh_token.py
@@ -19,7 +19,7 @@ class RefreshTokenGrant(GrantTypeBase):
"""`Refresh token grant`_
- .. _`Refresh token grant`: http://tools.ietf.org/html/rfc6749#section-6
+ .. _`Refresh token grant`: https://tools.ietf.org/html/rfc6749#section-6
"""
def __init__(self, request_validator=None,
@@ -46,8 +46,8 @@ class RefreshTokenGrant(GrantTypeBase):
identical to that of the refresh token included by the client in the
request.
- .. _`Section 5.1`: http://tools.ietf.org/html/rfc6749#section-5.1
- .. _`Section 5.2`: http://tools.ietf.org/html/rfc6749#section-5.2
+ .. _`Section 5.1`: https://tools.ietf.org/html/rfc6749#section-5.1
+ .. _`Section 5.2`: https://tools.ietf.org/html/rfc6749#section-5.2
"""
headers = {
'Content-Type': 'application/json',
@@ -90,7 +90,7 @@ class RefreshTokenGrant(GrantTypeBase):
# the client was issued client credentials (or assigned other
# authentication requirements), the client MUST authenticate with the
# authorization server as described in Section 3.2.1.
- # http://tools.ietf.org/html/rfc6749#section-3.2.1
+ # https://tools.ietf.org/html/rfc6749#section-3.2.1
if self.request_validator.client_authentication_required(request):
log.debug('Authenticating client, %r.', request)
if not self.request_validator.authenticate_client(request):
diff --git a/oauthlib/oauth2/rfc6749/grant_types/resource_owner_password_credentials.py b/oauthlib/oauth2/rfc6749/grant_types/resource_owner_password_credentials.py
index ede779a..e5f04af 100644
--- a/oauthlib/oauth2/rfc6749/grant_types/resource_owner_password_credentials.py
+++ b/oauthlib/oauth2/rfc6749/grant_types/resource_owner_password_credentials.py
@@ -67,7 +67,7 @@ class ResourceOwnerPasswordCredentialsGrant(GrantTypeBase):
the resource owner credentials, and if valid, issues an access
token.
- .. _`Resource Owner Password Credentials Grant`: http://tools.ietf.org/html/rfc6749#section-4.3
+ .. _`Resource Owner Password Credentials Grant`: https://tools.ietf.org/html/rfc6749#section-4.3
"""
def create_token_response(self, request, token_handler):
@@ -79,8 +79,8 @@ class ResourceOwnerPasswordCredentialsGrant(GrantTypeBase):
authentication or is invalid, the authorization server returns an
error response as described in `Section 5.2`_.
- .. _`Section 5.1`: http://tools.ietf.org/html/rfc6749#section-5.1
- .. _`Section 5.2`: http://tools.ietf.org/html/rfc6749#section-5.2
+ .. _`Section 5.1`: https://tools.ietf.org/html/rfc6749#section-5.1
+ .. _`Section 5.2`: https://tools.ietf.org/html/rfc6749#section-5.2
"""
headers = {
'Content-Type': 'application/json',
@@ -153,8 +153,8 @@ class ResourceOwnerPasswordCredentialsGrant(GrantTypeBase):
brute force attacks (e.g., using rate-limitation or generating
alerts).
- .. _`Section 3.3`: http://tools.ietf.org/html/rfc6749#section-3.3
- .. _`Section 3.2.1`: http://tools.ietf.org/html/rfc6749#section-3.2.1
+ .. _`Section 3.3`: https://tools.ietf.org/html/rfc6749#section-3.3
+ .. _`Section 3.2.1`: https://tools.ietf.org/html/rfc6749#section-3.2.1
"""
for validator in self.custom_validators.pre_token:
validator(request)
diff --git a/oauthlib/oauth2/rfc6749/parameters.py b/oauthlib/oauth2/rfc6749/parameters.py
index b87b146..0107933 100644
--- a/oauthlib/oauth2/rfc6749/parameters.py
+++ b/oauthlib/oauth2/rfc6749/parameters.py
@@ -5,7 +5,7 @@ oauthlib.oauth2.rfc6749.parameters
This module contains methods related to `Section 4`_ of the OAuth 2 RFC.
-.. _`Section 4`: http://tools.ietf.org/html/rfc6749#section-4
+.. _`Section 4`: https://tools.ietf.org/html/rfc6749#section-4
"""
from __future__ import absolute_import, unicode_literals
@@ -61,11 +61,11 @@ def prepare_grant_uri(uri, client_id, response_type, redirect_uri=None,
&redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb HTTP/1.1
Host: server.example.com
- .. _`W3C.REC-html401-19991224`: http://tools.ietf.org/html/rfc6749#ref-W3C.REC-html401-19991224
- .. _`Section 2.2`: http://tools.ietf.org/html/rfc6749#section-2.2
- .. _`Section 3.1.2`: http://tools.ietf.org/html/rfc6749#section-3.1.2
- .. _`Section 3.3`: http://tools.ietf.org/html/rfc6749#section-3.3
- .. _`section 10.12`: http://tools.ietf.org/html/rfc6749#section-10.12
+ .. _`W3C.REC-html401-19991224`: https://tools.ietf.org/html/rfc6749#ref-W3C.REC-html401-19991224
+ .. _`Section 2.2`: https://tools.ietf.org/html/rfc6749#section-2.2
+ .. _`Section 3.1.2`: https://tools.ietf.org/html/rfc6749#section-3.1.2
+ .. _`Section 3.3`: https://tools.ietf.org/html/rfc6749#section-3.3
+ .. _`section 10.12`: https://tools.ietf.org/html/rfc6749#section-10.12
"""
if not is_secure_transport(uri):
raise InsecureTransportError()
@@ -111,7 +111,7 @@ def prepare_token_request(grant_type, body='', **kwargs):
grant_type=authorization_code&code=SplxlOBeZQQYbYS6WxSbIA
&redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb
- .. _`Section 4.1.1`: http://tools.ietf.org/html/rfc6749#section-4.1.1
+ .. _`Section 4.1.1`: https://tools.ietf.org/html/rfc6749#section-4.1.1
"""
params = [('grant_type', grant_type)]
@@ -153,9 +153,9 @@ def prepare_token_revocation_request(url, token, token_type_hint="access_token",
specification MAY define other values for this parameter using the
registry defined in `Section 4.1.2`_.
- .. _`Section 1.4`: http://tools.ietf.org/html/rfc6749#section-1.4
- .. _`Section 1.5`: http://tools.ietf.org/html/rfc6749#section-1.5
- .. _`Section 4.1.2`: http://tools.ietf.org/html/rfc7009#section-4.1.2
+ .. _`Section 1.4`: https://tools.ietf.org/html/rfc6749#section-1.4
+ .. _`Section 1.5`: https://tools.ietf.org/html/rfc6749#section-1.5
+ .. _`Section 4.1.2`: https://tools.ietf.org/html/rfc7009#section-4.1.2
"""
if not is_secure_transport(url):
@@ -348,10 +348,10 @@ def parse_token_response(body, scope=None):
"example_parameter":"example_value"
}
- .. _`Section 7.1`: http://tools.ietf.org/html/rfc6749#section-7.1
- .. _`Section 6`: http://tools.ietf.org/html/rfc6749#section-6
- .. _`Section 3.3`: http://tools.ietf.org/html/rfc6749#section-3.3
- .. _`RFC4627`: http://tools.ietf.org/html/rfc4627
+ .. _`Section 7.1`: https://tools.ietf.org/html/rfc6749#section-7.1
+ .. _`Section 6`: https://tools.ietf.org/html/rfc6749#section-6
+ .. _`Section 3.3`: https://tools.ietf.org/html/rfc6749#section-3.3
+ .. _`RFC4627`: https://tools.ietf.org/html/rfc4627
"""
try:
params = json.loads(body)
@@ -359,7 +359,7 @@ def parse_token_response(body, scope=None):
# Fall back to URL-encoded string, to support old implementations,
# including (at time of writing) Facebook. See:
- # https://github.com/idan/oauthlib/issues/267
+ # https://github.com/oauthlib/oauthlib/issues/267
params = dict(urlparse.parse_qsl(body))
for key in ('expires_in', 'expires'):
@@ -395,7 +395,7 @@ def validate_token_parameters(params):
# If the issued access token scope is different from the one requested by
# the client, the authorization server MUST include the "scope" response
# parameter to inform the client of the actual scope granted.
- # http://tools.ietf.org/html/rfc6749#section-3.3
+ # https://tools.ietf.org/html/rfc6749#section-3.3
if params.scope_changed:
message = 'Scope has changed from "{old}" to "{new}".'.format(
old=params.old_scope, new=params.scope,
diff --git a/oauthlib/oauth2/rfc6749/request_validator.py b/oauthlib/oauth2/rfc6749/request_validator.py
index 4b76b7a..56ecc3d 100644
--- a/oauthlib/oauth2/rfc6749/request_validator.py
+++ b/oauthlib/oauth2/rfc6749/request_validator.py
@@ -34,9 +34,9 @@ class RequestValidator(object):
- Resource Owner Password Credentials Grant
- Refresh Token Grant
- .. _`Section 4.3.2`: http://tools.ietf.org/html/rfc6749#section-4.3.2
- .. _`Section 4.1.3`: http://tools.ietf.org/html/rfc6749#section-4.1.3
- .. _`Section 6`: http://tools.ietf.org/html/rfc6749#section-6
+ .. _`Section 4.3.2`: https://tools.ietf.org/html/rfc6749#section-4.3.2
+ .. _`Section 4.1.3`: https://tools.ietf.org/html/rfc6749#section-4.1.3
+ .. _`Section 6`: https://tools.ietf.org/html/rfc6749#section-6
"""
return True
@@ -60,7 +60,7 @@ class RequestValidator(object):
- Client Credentials Grant
- Refresh Token Grant
- .. _`HTTP Basic Authentication Scheme`: http://tools.ietf.org/html/rfc1945#section-11.1
+ .. _`HTTP Basic Authentication Scheme`: https://tools.ietf.org/html/rfc1945#section-11.1
"""
raise NotImplementedError('Subclasses must implement this method.')
@@ -82,7 +82,7 @@ class RequestValidator(object):
"""
raise NotImplementedError('Subclasses must implement this method.')
- def confirm_redirect_uri(self, client_id, code, redirect_uri, client,
+ def confirm_redirect_uri(self, client_id, code, redirect_uri, client, request,
*args, **kwargs):
"""Ensure that the authorization process represented by this authorization
code began with this 'redirect_uri'.
@@ -352,8 +352,24 @@ class RequestValidator(object):
"""
raise NotImplementedError('Subclasses must implement this method.')
- def get_id_token(self, token, token_handler, request):
+ def get_jwt_bearer_token(self, token, token_handler, request):
+ """Get JWT Bearer token or OpenID Connect ID token
+
+ If using OpenID Connect this SHOULD call `oauthlib.oauth2.RequestValidator.get_id_token`
+
+ :param token: A Bearer token dict
+ :param token_handler: the token handler (BearerToken class)
+ :param request: the HTTP Request (oauthlib.common.Request)
+ :return: The JWT Bearer token or OpenID Connect ID token (a JWS signed JWT)
+
+ Method is used by JWT Bearer and OpenID Connect tokens:
+ - JWTToken.create_token
"""
+ raise NotImplementedError('Subclasses must implement this method.')
+
+ def get_id_token(self, token, token_handler, request):
+ """Get OpenID Connect ID token
+
In the OpenID Connect workflows when an ID Token is requested this method is called.
Subclasses should implement the construction, signing and optional encryption of the
ID Token as described in the OpenID Connect spec.
@@ -384,6 +400,52 @@ class RequestValidator(object):
# the request.scope should be used by the get_id_token() method to determine which claims to include in the resulting id_token
raise NotImplementedError('Subclasses must implement this method.')
+ def validate_jwt_bearer_token(self, token, scopes, request):
+ """Ensure the JWT Bearer token or OpenID Connect ID token are valids and authorized access to scopes.
+
+ If using OpenID Connect this SHOULD call `oauthlib.oauth2.RequestValidator.get_id_token`
+
+ If not using OpenID Connect this can `return None` to avoid 5xx rather 401/3 response.
+
+ OpenID connect core 1.0 describe how to validate an id_token:
+ - http://openid.net/specs/openid-connect-core-1_0.html#IDTokenValidation
+ - http://openid.net/specs/openid-connect-core-1_0.html#ImplicitIDTValidation
+ - http://openid.net/specs/openid-connect-core-1_0.html#HybridIDTValidation
+ - http://openid.net/specs/openid-connect-core-1_0.html#HybridIDTValidation2
+
+ :param token: Unicode Bearer token
+ :param scopes: List of scopes (defined by you)
+ :param request: The HTTP Request (oauthlib.common.Request)
+ :rtype: True or False
+
+ Method is indirectly used by all core OpenID connect JWT token issuing grant types:
+ - Authorization Code Grant
+ - Implicit Grant
+ - Hybrid Grant
+ """
+ raise NotImplementedError('Subclasses must implement this method.')
+
+ def validate_id_token(self, token, scopes, request):
+ """Ensure the id token is valid and authorized access to scopes.
+
+ OpenID connect core 1.0 describe how to validate an id_token:
+ - http://openid.net/specs/openid-connect-core-1_0.html#IDTokenValidation
+ - http://openid.net/specs/openid-connect-core-1_0.html#ImplicitIDTValidation
+ - http://openid.net/specs/openid-connect-core-1_0.html#HybridIDTValidation
+ - http://openid.net/specs/openid-connect-core-1_0.html#HybridIDTValidation2
+
+ :param token: Unicode Bearer token
+ :param scopes: List of scopes (defined by you)
+ :param request: The HTTP Request (oauthlib.common.Request)
+ :rtype: True or False
+
+ Method is indirectly used by all core OpenID connect JWT token issuing grant types:
+ - Authorization Code Grant
+ - Implicit Grant
+ - Hybrid Grant
+ """
+ raise NotImplementedError('Subclasses must implement this method.')
+
def validate_bearer_token(self, token, scopes, request):
"""Ensure the Bearer token is valid and authorized access to scopes.
diff --git a/oauthlib/oauth2/rfc6749/tokens.py b/oauthlib/oauth2/rfc6749/tokens.py
index e0ac431..a7491f4 100644
--- a/oauthlib/oauth2/rfc6749/tokens.py
+++ b/oauthlib/oauth2/rfc6749/tokens.py
@@ -4,8 +4,8 @@ oauthlib.oauth2.rfc6749.tokens
This module contains methods for adding two types of access tokens to requests.
-- Bearer http://tools.ietf.org/html/rfc6750
-- MAC http://tools.ietf.org/html/draft-ietf-oauth-v2-http-mac-01
+- 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
@@ -24,8 +24,6 @@ except ImportError:
from urllib.parse import urlparse
-
-
class OAuth2Token(dict):
def __init__(self, params, old_scope=None):
@@ -95,8 +93,8 @@ def prepare_mac_header(token, uri, key, http_method,
nonce="1336363200:dj83hs9s",
mac="bhCQXTVyfj5cmA9uKkPFx1zeOXM="
- .. _`MAC Access Authentication`: http://tools.ietf.org/html/draft-ietf-oauth-v2-http-mac-01
- .. _`extension algorithms`: http://tools.ietf.org/html/draft-ietf-oauth-v2-http-mac-01#section-7.1
+ .. _`MAC Access Authentication`: https://tools.ietf.org/html/draft-ietf-oauth-v2-http-mac-01
+ .. _`extension algorithms`: https://tools.ietf.org/html/draft-ietf-oauth-v2-http-mac-01#section-7.1
:param uri: Request URI.
:param headers: Request headers as a dictionary.
@@ -182,7 +180,7 @@ def prepare_bearer_uri(token, uri):
http://www.example.com/path?access_token=h480djs93hd8
- .. _`Bearer Token`: http://tools.ietf.org/html/rfc6750
+ .. _`Bearer Token`: https://tools.ietf.org/html/rfc6750
"""
return add_params_to_uri(uri, [(('access_token', token))])
@@ -193,7 +191,7 @@ def prepare_bearer_headers(token, headers=None):
Authorization: Bearer h480djs93hd8
- .. _`Bearer Token`: http://tools.ietf.org/html/rfc6750
+ .. _`Bearer Token`: https://tools.ietf.org/html/rfc6750
"""
headers = headers or {}
headers['Authorization'] = 'Bearer %s' % token
@@ -205,7 +203,7 @@ def prepare_bearer_body(token, body=''):
access_token=h480djs93hd8
- .. _`Bearer Token`: http://tools.ietf.org/html/rfc6750
+ .. _`Bearer Token`: https://tools.ietf.org/html/rfc6750
"""
return add_params_to_qs(body, [(('access_token', token))])
@@ -222,6 +220,24 @@ def signed_token_generator(private_pem, **kwargs):
return signed_token_generator
+def get_token_from_header(request):
+ """
+ Helper function to extract a token from the request header.
+ :param request: The request object
+ :return: Return the token or None if the Authorization header is malformed.
+ """
+ token = None
+
+ if 'Authorization' in request.headers:
+ split_header = request.headers.get('Authorization').split()
+ if len(split_header) == 2 and split_header[0] == 'Bearer':
+ token = split_header[1]
+ else:
+ token = request.access_token
+
+ return token
+
+
class TokenBase(object):
def __call__(self, request, refresh_token=False):
@@ -288,18 +304,54 @@ class BearerToken(TokenBase):
return token
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_bearer_token(
token, request.scopes, request)
def estimate_type(self, request):
- if request.headers.get('Authorization', '').startswith('Bearer'):
+ if request.headers.get('Authorization', '').split(' ')[0] == 'Bearer':
return 9
elif request.access_token is not None:
return 5
else:
return 0
+
+
+class JWTToken(TokenBase):
+ __slots__ = (
+ 'request_validator', 'token_generator',
+ 'refresh_token_generator', 'expires_in'
+ )
+
+ def __init__(self, request_validator=None, token_generator=None,
+ expires_in=None, refresh_token_generator=None):
+ self.request_validator = request_validator
+ self.token_generator = token_generator or random_token_generator
+ self.refresh_token_generator = (
+ refresh_token_generator or self.token_generator
+ )
+ self.expires_in = expires_in or 3600
+
+ def create_token(self, request, refresh_token=False, save_token=False):
+ """Create a JWT Token, using requestvalidator method."""
+
+ if callable(self.expires_in):
+ expires_in = self.expires_in(request)
+ else:
+ expires_in = self.expires_in
+
+ request.expires_in = expires_in
+
+ return self.request_validator.get_jwt_bearer_token(None, None, request)
+
+ def validate_request(self, request):
+ token = get_token_from_header(request)
+ return self.request_validator.validate_jwt_bearer_token(
+ token, request.scopes, request)
+
+ def estimate_type(self, request):
+ split_header = request.headers.get('Authorization', '').split()
+
+ if len(split_header) == 2 and split_header[0] == 'Bearer' and split_header[1].startswith('ey') and split_header[1].count('.') in (2, 4):
+ return 10
+ return 0
diff --git a/oauthlib/signals.py b/oauthlib/signals.py
index 2f86650..22d47a4 100644
--- a/oauthlib/signals.py
+++ b/oauthlib/signals.py
@@ -8,7 +8,7 @@ signals_available = False
try:
from blinker import Namespace
signals_available = True
-except ImportError:
+except ImportError: # noqa
class Namespace(object):
def signal(self, name, doc=None):
return _FakeSignal(name, doc)
diff --git a/requirements-test.txt b/requirements-test.txt
index e761883..5bf6e06 100644
--- a/requirements-test.txt
+++ b/requirements-test.txt
@@ -1,4 +1,4 @@
-r requirements.txt
coverage>=3.7.1
nose==1.3.7
-mock==1.0.1
+mock>=2.0
diff --git a/requirements.txt b/requirements.txt
index e4980c7..a4614bb 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,3 +1,3 @@
-pyjwt==1.0.0
-blinker==1.3
-cryptography>=0.8.1
+pyjwt==1.6.0
+blinker==1.4
+cryptography>=1.4.0
diff --git a/setup.py b/setup.py
index 43f4d95..0c4e564 100755
--- a/setup.py
+++ b/setup.py
@@ -21,7 +21,7 @@ def fread(fn):
if sys.version_info[0] == 3:
tests_require = ['nose', 'cryptography', 'pyjwt>=1.0.0', 'blinker']
else:
- tests_require = ['nose', 'unittest2', 'cryptography', 'mock', 'pyjwt>=1.0.0', 'blinker']
+ tests_require = ['nose', 'unittest2', 'cryptography', 'mock>=2.0', 'pyjwt>=1.0.0', 'blinker']
rsa_require = ['cryptography']
signedtoken_require = ['cryptography', 'pyjwt>=1.0.0']
signals_require = ['blinker']
@@ -37,7 +37,7 @@ setup(
author_email='idan@gazit.me',
maintainer='Ib Lundgren',
maintainer_email='ib.lundgren@gmail.com',
- url='https://github.com/idan/oauthlib',
+ url='https://github.com/oauthlib/oauthlib',
platforms='any',
license='BSD',
packages=find_packages(exclude=('docs', 'tests', 'tests.*')),
diff --git a/tests/oauth2/rfc6749/clients/test_base.py b/tests/oauth2/rfc6749/clients/test_base.py
index c788bc1..d48a944 100644
--- a/tests/oauth2/rfc6749/clients/test_base.py
+++ b/tests/oauth2/rfc6749/clients/test_base.py
@@ -4,7 +4,7 @@ from __future__ import absolute_import, unicode_literals
import datetime
from oauthlib import common
-from oauthlib.oauth2 import Client, InsecureTransportError
+from oauthlib.oauth2 import Client, InsecureTransportError, TokenExpiredError
from oauthlib.oauth2.rfc6749 import utils
from oauthlib.oauth2.rfc6749.clients import AUTH_HEADER, BODY, URI_QUERY
@@ -51,10 +51,26 @@ class ClientTest(TestCase):
self.assertFormBodyEqual(body, self.body)
self.assertEqual(headers, self.bearer_header)
+ # Non-HTTPS
+ insecure_uri = 'http://example.com/path?query=world'
+ client = Client(self.client_id, access_token=self.access_token, token_type="Bearer")
+ self.assertRaises(InsecureTransportError, client.add_token, insecure_uri,
+ body=self.body,
+ headers=self.headers)
+
# Missing access token
client = Client(self.client_id)
self.assertRaises(ValueError, client.add_token, self.uri)
+ # Expired token
+ expired = 523549800
+ expired_token = {
+ 'expires_at': expired,
+ }
+ client = Client(self.client_id, token=expired_token, access_token=self.access_token, token_type="Bearer")
+ self.assertRaises(TokenExpiredError, client.add_token, self.uri,
+ body=self.body, headers=self.headers)
+
# The default token placement, bearer in auth header
client = Client(self.client_id, access_token=self.access_token)
uri, headers, body = client.add_token(self.uri, body=self.body,
@@ -150,8 +166,26 @@ class ClientTest(TestCase):
self.assertEqual(uri, self.uri)
self.assertEqual(body, self.body)
self.assertEqual(headers, self.mac_00_header)
+ # Non-HTTPS
+ insecure_uri = 'http://example.com/path?query=world'
+ self.assertRaises(InsecureTransportError, client.add_token, insecure_uri,
+ body=self.body,
+ headers=self.headers,
+ issue_time=datetime.datetime.now())
+ # Expired Token
+ expired = 523549800
+ expired_token = {
+ 'expires_at': expired,
+ }
+ client = Client(self.client_id, token=expired_token, token_type="MAC",
+ access_token=self.access_token, mac_key=self.mac_key,
+ mac_algorithm="hmac-sha-1")
+ self.assertRaises(TokenExpiredError, client.add_token, self.uri,
+ body=self.body,
+ headers=self.headers,
+ issue_time=datetime.datetime.now())
- # Add the Authorization header (draft 00)
+ # Add the Authorization header (draft 01)
client = Client(self.client_id, token_type="MAC",
access_token=self.access_token, mac_key=self.mac_key,
mac_algorithm="hmac-sha-1")
@@ -160,7 +194,24 @@ class ClientTest(TestCase):
self.assertEqual(uri, self.uri)
self.assertEqual(body, self.body)
self.assertEqual(headers, self.mac_01_header)
-
+ # Non-HTTPS
+ insecure_uri = 'http://example.com/path?query=world'
+ self.assertRaises(InsecureTransportError, client.add_token, insecure_uri,
+ body=self.body,
+ headers=self.headers,
+ draft=1)
+ # Expired Token
+ expired = 523549800
+ expired_token = {
+ 'expires_at': expired,
+ }
+ client = Client(self.client_id, token=expired_token, token_type="MAC",
+ access_token=self.access_token, mac_key=self.mac_key,
+ mac_algorithm="hmac-sha-1")
+ self.assertRaises(TokenExpiredError, client.add_token, self.uri,
+ body=self.body,
+ headers=self.headers,
+ draft=1)
def test_revocation_request(self):
client = Client(self.client_id)
@@ -208,6 +259,21 @@ class ClientTest(TestCase):
# NotImplementedError
self.assertRaises(NotImplementedError, client.prepare_authorization_request, auth_url)
+ def test_prepare_token_request(self):
+ redirect_url = 'https://example.com/callback/'
+ scopes = 'read'
+ token_url = 'https://example.com/token/'
+ state = 'fake_state'
+
+ client = Client(self.client_id, scope=scopes, state=state)
+
+ # Non-HTTPS
+ self.assertRaises(InsecureTransportError,
+ client.prepare_token_request, 'http://example.com/token/')
+
+ # NotImplementedError
+ self.assertRaises(NotImplementedError, client.prepare_token_request, token_url)
+
def test_prepare_refresh_token_request(self):
client = Client(self.client_id)
diff --git a/tests/oauth2/rfc6749/clients/test_mobile_application.py b/tests/oauth2/rfc6749/clients/test_mobile_application.py
index 309220b..51e4dab 100644
--- a/tests/oauth2/rfc6749/clients/test_mobile_application.py
+++ b/tests/oauth2/rfc6749/clients/test_mobile_application.py
@@ -69,6 +69,18 @@ class MobileApplicationClientTest(TestCase):
uri = client.prepare_request_uri(self.uri, **self.kwargs)
self.assertURLEqual(uri, self.uri_kwargs)
+ def test_populate_attributes(self):
+
+ client = MobileApplicationClient(self.client_id)
+
+ response_uri = (self.response_uri + "&code=EVIL-CODE")
+
+ client.parse_request_uri_response(response_uri, scope=self.scope)
+
+ # We must not accidentally pick up any further security
+ # credentials at this point.
+ self.assertIsNone(client.code)
+
def test_parse_token_response(self):
client = MobileApplicationClient(self.client_id)
diff --git a/tests/oauth2/rfc6749/clients/test_service_application.py b/tests/oauth2/rfc6749/clients/test_service_application.py
index 2dc633a..dc337cf 100644
--- a/tests/oauth2/rfc6749/clients/test_service_application.py
+++ b/tests/oauth2/rfc6749/clients/test_service_application.py
@@ -89,8 +89,8 @@ mfvGGg3xNjTMO7IdrwIDAQAB
audience=self.audience,
body=self.body)
r = Request('https://a.b', body=body)
- self.assertEqual(r.isnot, 'empty')
- self.assertEqual(r.grant_type, ServiceApplicationClient.grant_type)
+ self.assertEqual(r.isnot, 'empty')
+ self.assertEqual(r.grant_type, ServiceApplicationClient.grant_type)
claim = jwt.decode(r.assertion, self.public_key, audience=self.audience, algorithms=['RS256'])
@@ -98,6 +98,72 @@ mfvGGg3xNjTMO7IdrwIDAQAB
# audience verification is handled during decode now
self.assertEqual(claim['sub'], self.subject)
self.assertEqual(claim['iat'], int(t.return_value))
+ self.assertNotIn('nbf', claim)
+ self.assertNotIn('jti', claim)
+
+ # Missing issuer parameter
+ self.assertRaises(ValueError, client.prepare_request_body,
+ issuer=None, subject=self.subject, audience=self.audience, body=self.body)
+
+ # Missing subject parameter
+ self.assertRaises(ValueError, client.prepare_request_body,
+ issuer=self.issuer, subject=None, audience=self.audience, body=self.body)
+
+ # Missing audience parameter
+ self.assertRaises(ValueError, client.prepare_request_body,
+ issuer=self.issuer, subject=self.subject, audience=None, body=self.body)
+
+ # Optional kwargs
+ not_before = time() - 3600
+ jwt_id = '8zd15df4s35f43sd'
+ body = client.prepare_request_body(issuer=self.issuer,
+ subject=self.subject,
+ audience=self.audience,
+ body=self.body,
+ not_before=not_before,
+ jwt_id=jwt_id)
+
+ r = Request('https://a.b', body=body)
+ self.assertEqual(r.isnot, 'empty')
+ self.assertEqual(r.grant_type, ServiceApplicationClient.grant_type)
+
+ claim = jwt.decode(r.assertion, self.public_key, audience=self.audience, algorithms=['RS256'])
+
+ self.assertEqual(claim['iss'], self.issuer)
+ # audience verification is handled during decode now
+ self.assertEqual(claim['sub'], self.subject)
+ self.assertEqual(claim['iat'], int(t.return_value))
+ self.assertEqual(claim['nbf'], not_before)
+ self.assertEqual(claim['jti'], jwt_id)
+
+ @patch('time.time')
+ def test_request_body_no_initial_private_key(self, t):
+ t.return_value = time()
+ self.token['expires_at'] = self.token['expires_in'] + t.return_value
+
+ client = ServiceApplicationClient(
+ self.client_id, private_key=None)
+
+ # Basic with private key provided
+ body = client.prepare_request_body(issuer=self.issuer,
+ subject=self.subject,
+ audience=self.audience,
+ body=self.body,
+ private_key=self.private_key)
+ r = Request('https://a.b', body=body)
+ self.assertEqual(r.isnot, 'empty')
+ self.assertEqual(r.grant_type, ServiceApplicationClient.grant_type)
+
+ claim = jwt.decode(r.assertion, self.public_key, audience=self.audience, algorithms=['RS256'])
+
+ self.assertEqual(claim['iss'], self.issuer)
+ # audience verification is handled during decode now
+ self.assertEqual(claim['sub'], self.subject)
+ self.assertEqual(claim['iat'], int(t.return_value))
+
+ # No private key provided
+ self.assertRaises(ValueError, client.prepare_request_body,
+ issuer=self.issuer, subject=self.subject, audience=self.audience, body=self.body)
@patch('time.time')
def test_parse_token_response(self, t):
diff --git a/tests/oauth2/rfc6749/clients/test_web_application.py b/tests/oauth2/rfc6749/clients/test_web_application.py
index 85b247d..4ecc3b3 100644
--- a/tests/oauth2/rfc6749/clients/test_web_application.py
+++ b/tests/oauth2/rfc6749/clients/test_web_application.py
@@ -38,7 +38,7 @@ class WebApplicationClientTest(TestCase):
code = "zzzzaaaa"
body = "not=empty"
- body_code = "not=empty&grant_type=authorization_code&code=%s&client_id=%s" % (code, client_id)
+ body_code = "not=empty&grant_type=authorization_code&code=%s" % code
body_redirect = body_code + "&redirect_uri=http%3A%2F%2Fmy.page.com%2Fcallback"
body_kwargs = body_code + "&some=providers&require=extra+arguments"
@@ -117,6 +117,25 @@ class WebApplicationClientTest(TestCase):
self.response_uri,
state="invalid")
+ def test_populate_attributes(self):
+
+ client = WebApplicationClient(self.client_id)
+
+ response_uri = (self.response_uri +
+ "&access_token=EVIL-TOKEN"
+ "&refresh_token=EVIL-TOKEN"
+ "&mac_key=EVIL-KEY")
+
+ client.parse_request_uri_response(response_uri, self.state)
+
+ self.assertEqual(client.code, self.code)
+
+ # We must not accidentally pick up any further security
+ # credentials at this point.
+ self.assertIsNone(client.access_token)
+ self.assertIsNone(client.refresh_token)
+ self.assertIsNone(client.mac_key)
+
def test_parse_token_response(self):
client = WebApplicationClient(self.client_id)
diff --git a/tests/oauth2/rfc6749/test_tokens.py b/tests/oauth2/rfc6749/test_tokens.py
index e2e558d..ecac03e 100644
--- a/tests/oauth2/rfc6749/test_tokens.py
+++ b/tests/oauth2/rfc6749/test_tokens.py
@@ -1,5 +1,8 @@
from __future__ import absolute_import, unicode_literals
+import mock
+
+from oauthlib.common import Request
from oauthlib.oauth2.rfc6749.tokens import *
from ...unittest import TestCase
@@ -59,9 +62,22 @@ class TokenTest(TestCase):
bearer_headers = {
'Authorization': 'Bearer vF9dft4qmT'
}
+ fake_bearer_headers = [
+ {'Authorization': 'Beaver vF9dft4qmT'},
+ {'Authorization': 'BeavervF9dft4qmT'},
+ {'Authorization': 'Beaver vF9dft4qmT'},
+ {'Authorization': 'BearerF9dft4qmT'},
+ {'Authorization': 'Bearer vF9d ft4qmT'},
+ ]
+ valid_header_with_multiple_spaces = {'Authorization': 'Bearer vF9dft4qmT'}
bearer_body = 'access_token=vF9dft4qmT'
bearer_uri = 'http://server.example.com/resource?access_token=vF9dft4qmT'
+ def _mocked_validate_bearer_token(self, token, scopes, request):
+ if not token:
+ return False
+ return True
+
def test_prepare_mac_header(self):
"""Verify mac signatures correctness
@@ -80,3 +96,196 @@ class TokenTest(TestCase):
self.assertEqual(prepare_bearer_headers(self.token), self.bearer_headers)
self.assertEqual(prepare_bearer_body(self.token), self.bearer_body)
self.assertEqual(prepare_bearer_uri(self.token, uri=self.uri), self.bearer_uri)
+
+ def test_fake_bearer_is_not_validated(self):
+ request_validator = mock.MagicMock()
+ request_validator.validate_bearer_token = self._mocked_validate_bearer_token
+
+ for fake_header in self.fake_bearer_headers:
+ request = Request('/', headers=fake_header)
+ result = BearerToken(request_validator=request_validator).validate_request(request)
+
+ self.assertFalse(result)
+
+ def test_header_with_multispaces_is_validated(self):
+ request_validator = mock.MagicMock()
+ request_validator.validate_bearer_token = self._mocked_validate_bearer_token
+
+ request = Request('/', headers=self.valid_header_with_multiple_spaces)
+ result = BearerToken(request_validator=request_validator).validate_request(request)
+
+ self.assertTrue(result)
+
+ def test_estimate_type_with_fake_header_returns_type_0(self):
+ request_validator = mock.MagicMock()
+ request_validator.validate_bearer_token = self._mocked_validate_bearer_token
+
+ for fake_header in self.fake_bearer_headers:
+ request = Request('/', headers=fake_header)
+ result = BearerToken(request_validator=request_validator).estimate_type(request)
+
+ if fake_header['Authorization'].count(' ') == 2 and \
+ fake_header['Authorization'].split()[0] == 'Bearer':
+ # If we're dealing with the header containing 2 spaces, it will be recognized
+ # as a Bearer valid header, the token itself will be invalid by the way.
+ self.assertEqual(result, 9)
+ else:
+ self.assertEqual(result, 0)
+
+
+class JWTTokenTestCase(TestCase):
+ fake_bearer_headers = [
+ {'Authorization': 'Beaver vF9dft4qmT'},
+ {'Authorization': 'BeavervF9dft4qmT'},
+ {'Authorization': 'Beaver vF9dft4qmT'},
+ {'Authorization': 'BearerF9dft4qmT'},
+ {'Authorization': 'Bearer vF9df t4qmT'},
+ ]
+
+ valid_header_with_multiple_spaces = {'Authorization': 'Bearer vF9dft4qmT'}
+
+ def _mocked_validate_bearer_token(self, token, scopes, request):
+ if not token:
+ return False
+ return True
+
+ def test_create_token_callable_expires_in(self):
+ """
+ Test retrieval of the expires in value by calling the callable expires_in property
+ """
+
+ expires_in_mock = mock.MagicMock()
+ request_mock = mock.MagicMock()
+
+ token = JWTToken(expires_in=expires_in_mock, request_validator=mock.MagicMock())
+ token.create_token(request=request_mock)
+
+ expires_in_mock.assert_called_once_with(request_mock)
+
+ def test_create_token_non_callable_expires_in(self):
+ """
+ When a non callable expires in is set this should just be set to the request
+ """
+
+ expires_in_mock = mock.NonCallableMagicMock()
+ request_mock = mock.MagicMock()
+
+ token = JWTToken(expires_in=expires_in_mock, request_validator=mock.MagicMock())
+ token.create_token(request=request_mock)
+
+ self.assertFalse(expires_in_mock.called)
+ self.assertEqual(request_mock.expires_in, expires_in_mock)
+
+ def test_create_token_calls_get_id_token(self):
+ """
+ When create_token is called the call should be forwarded to the get_id_token on the token validator
+ """
+ request_mock = mock.MagicMock()
+
+ with mock.patch('oauthlib.oauth2.rfc6749.request_validator.RequestValidator',
+ autospec=True) as RequestValidatorMock:
+
+ request_validator = RequestValidatorMock()
+
+ token = JWTToken(expires_in=mock.MagicMock(), request_validator=request_validator)
+ token.create_token(request=request_mock)
+
+ request_validator.get_jwt_bearer_token.assert_called_once_with(None, None, request_mock)
+
+ def test_validate_request_token_from_headers(self):
+ """
+ Bearer token get retrieved from headers.
+ """
+
+ with mock.patch('oauthlib.common.Request', autospec=True) as RequestMock, \
+ mock.patch('oauthlib.oauth2.rfc6749.request_validator.RequestValidator',
+ autospec=True) as RequestValidatorMock:
+ request_validator_mock = RequestValidatorMock()
+
+ token = JWTToken(request_validator=request_validator_mock)
+
+ request = RequestMock('/uri')
+ # Scopes is retrieved using the __call__ method which is not picked up correctly by mock.patch
+ # with autospec=True
+ request.scopes = mock.MagicMock()
+ request.headers = {
+ 'Authorization': 'Bearer some-token-from-header'
+ }
+
+ token.validate_request(request=request)
+
+ request_validator_mock.validate_jwt_bearer_token.assert_called_once_with('some-token-from-header',
+ request.scopes,
+ request)
+
+ def test_validate_token_from_request(self):
+ """
+ Token get retrieved from request object.
+ """
+
+ with mock.patch('oauthlib.common.Request', autospec=True) as RequestMock, \
+ mock.patch('oauthlib.oauth2.rfc6749.request_validator.RequestValidator',
+ autospec=True) as RequestValidatorMock:
+ request_validator_mock = RequestValidatorMock()
+
+ token = JWTToken(request_validator=request_validator_mock)
+
+ request = RequestMock('/uri')
+ # Scopes is retrieved using the __call__ method which is not picked up correctly by mock.patch
+ # with autospec=True
+ request.scopes = mock.MagicMock()
+ request.access_token = 'some-token-from-request-object'
+ request.headers = {}
+
+ token.validate_request(request=request)
+
+ request_validator_mock.validate_jwt_bearer_token.assert_called_once_with('some-token-from-request-object',
+ request.scopes,
+ request)
+
+ def test_fake_bearer_is_not_validated(self):
+ request_validator = mock.MagicMock()
+ request_validator.validate_jwt_bearer_token = self._mocked_validate_bearer_token
+
+ for fake_header in self.fake_bearer_headers:
+ request = Request('/', headers=fake_header)
+ result = JWTToken(request_validator=request_validator).validate_request(request)
+
+ self.assertFalse(result)
+
+ def test_header_with_multiple_spaces_is_validated(self):
+ request_validator = mock.MagicMock()
+ request_validator.validate_jwt_bearer_token = self._mocked_validate_bearer_token
+ request = Request('/', headers=self.valid_header_with_multiple_spaces)
+ result = JWTToken(request_validator=request_validator).validate_request(request)
+
+ self.assertTrue(result)
+
+ def test_estimate_type(self):
+ """
+ Estimate type results for a jwt token
+ """
+
+ def test_token(token, expected_result):
+ with mock.patch('oauthlib.common.Request', autospec=True) as RequestMock:
+ jwt_token = JWTToken()
+
+ request = RequestMock('/uri')
+ # Scopes is retrieved using the __call__ method which is not picked up correctly by mock.patch
+ # with autospec=True
+ request.headers = {
+ 'Authorization': 'Bearer {}'.format(token)
+ }
+
+ result = jwt_token.estimate_type(request=request)
+
+ self.assertEqual(result, expected_result)
+
+ test_items = (
+ ('eyfoo.foo.foo', 10),
+ ('eyfoo.foo.foo.foo.foo', 10),
+ ('eyfoobar', 0)
+ )
+
+ for token, expected_result in test_items:
+ test_token(token, expected_result)
diff --git a/tox.ini b/tox.ini
index a53676f..3dded41 100644
--- a/tox.ini
+++ b/tox.ini
@@ -1,5 +1,5 @@
[tox]
-envlist = py27,py34,py35,py36,pypy
+envlist = py27,py34,py35,py36,pypy,docs
[testenv]
deps=
@@ -9,3 +9,13 @@ commands=nosetests --with-coverage --cover-erase --cover-package=oauthlib -w tes
[testenv:py27]
deps=unittest2
{[testenv]deps}
+
+# tox -e docs to mimick readthedocs build.
+# as of today, RTD is using python2.7 and doesn't run "setup.py install"
+[testenv:docs]
+basepython=python2.7
+skipsdist=True
+deps=sphinx
+changedir=docs
+whitelist_externals=make
+commands=make clean html