summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJonathan Huot <JonathanHuot@users.noreply.github.com>2018-09-07 23:27:09 +0200
committerGitHub <noreply@github.com>2018-09-07 23:27:09 +0200
commit7ae5ed1a366f76f0edaf76c2429a81a6cb794d40 (patch)
tree0f4e404c8441a42c9c00d31c382763b50918c4cc
parentf38dd4ba5da15756ea6c4c46c775983d55f6f6b9 (diff)
parent36e7f50049f3333db72ebcb82677b465ec09f84b (diff)
downloadoauthlib-fix-oidc-tests.tar.gz
Merge branch 'master' into fix-oidc-testsfix-oidc-tests
-rw-r--r--.gitignore2
-rw-r--r--CODE_OF_CONDUCT.md28
-rw-r--r--LICENSE4
-rw-r--r--README.rst23
-rw-r--r--docs/conf.py2
-rw-r--r--docs/contributing.rst42
-rw-r--r--docs/faq.rst14
-rw-r--r--docs/oauth1/server.rst4
-rw-r--r--docs/oauth2/grants/jwt.rst17
-rw-r--r--docs/oauth2/preconfigured_servers.rst6
-rw-r--r--docs/oauth2/server.rst4
-rw-r--r--docs/oauth2/tokens/bearer.rst116
-rw-r--r--docs/oauth2/tokens/tokens.rst3
-rw-r--r--docs/release_process.rst10
-rw-r--r--oauthlib/__init__.py2
-rw-r--r--oauthlib/common.py3
-rw-r--r--oauthlib/oauth2/rfc6749/grant_types/authorization_code.py25
-rw-r--r--oauthlib/oauth2/rfc6749/grant_types/implicit.py5
-rw-r--r--oauthlib/oauth2/rfc6749/parameters.py4
-rw-r--r--oauthlib/oauth2/rfc6749/request_validator.py1
-rwxr-xr-xsetup.py2
-rw-r--r--tests/oauth2/rfc6749/clients/test_mobile_application.py2
-rw-r--r--tests/oauth2/rfc6749/endpoints/test_base_endpoint.py4
-rw-r--r--tests/oauth2/rfc6749/endpoints/test_credentials_preservation.py21
-rw-r--r--tests/oauth2/rfc6749/endpoints/test_error_responses.py26
-rw-r--r--tests/oauth2/rfc6749/grant_types/test_authorization_code.py6
-rw-r--r--tests/oauth2/rfc6749/test_parameters.py2
-rw-r--r--tests/test_common.py7
-rw-r--r--tox.ini13
29 files changed, 336 insertions, 62 deletions
diff --git a/.gitignore b/.gitignore
index 683f357..6f24649 100644
--- a/.gitignore
+++ b/.gitignore
@@ -33,6 +33,8 @@ htmlcov*
# Local file cruft/auto-backups
.DS_Store
*~
+**/#*#
+**/.#*
# Sphinx
docs/_build
diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md
new file mode 100644
index 0000000..3f242ff
--- /dev/null
+++ b/CODE_OF_CONDUCT.md
@@ -0,0 +1,28 @@
+# OAuthlib Code of Conduct
+
+Like the technical community as a whole, the OAuthlib team and community is made up of a mixture of professionals and volunteers from all over the world, working on every aspect of the mission - including mentorship, teaching, and connecting people.
+
+Diversity is one of our huge strengths, but it can also lead to communication issues and unhappiness. To that end, we have a few ground rules that we ask people to adhere to. This code applies equally to founders, mentors and those seeking help and guidance.
+
+This isn't an exhaustive list of things that you can't do. Rather, take it in the spirit in which it's intended - a guide to make it easier to enrich all of us and the technical communities in which we participate.
+
+This code of conduct applies to all spaces managed by the OAuthlib project. This includes Gitter, the mailing lists, the issue tracker, and any other forums created by the project team which the community uses for communication. In addition, violations of this code outside these spaces may affect a person's ability to participate within them.
+
+If you believe someone is violating the code of conduct, we ask that you report it by contacting us.
+
+ Be friendly and patient.
+ Be welcoming. We strive to be a community that welcomes and supports people of all backgrounds and identities. This includes, but is not limited to members of any race, ethnicity, culture, national origin, colour, immigration status, social and economic class, educational level, sex, sexual orientation, gender identity and expression, age, size, family status, political belief, religion, and mental and physical ability.
+ Be considerate. Your work will be used by other people, and you in turn will depend on the work of others. Any decision you take will affect users and colleagues, and you should take those consequences into account when making decisions. Remember that we're a world-wide community, so you might not be communicating in someone else's primary language.
+ Be respectful. Not all of us will agree all the time, but disagreement is no excuse for poor behavior and poor manners. We might all experience some frustration now and then, but we cannot allow that frustration to turn into a personal attack. It's important to remember that a community where people feel uncomfortable or threatened is not a productive one. Members of the OAuthlib community should be respectful when dealing with other members as well as with people outside the OAuthlib community.
+ Be careful in the words that you choose. We are a community of professionals, and we conduct ourselves professionally. Be kind to others. Do not insult or put down other participants. Harassment and other exclusionary behavior aren't acceptable. This includes, but is not limited to:
+ Violent threats or language directed against another person.
+ Discriminatory jokes and language.
+ Posting sexually explicit or violent material.
+ Posting (or threatening to post) other people's personally identifying information ("doxing").
+ Personal insults, especially those using racist or sexist terms.
+ Unwelcome sexual attention.
+ Advocating for, or encouraging, any of the above behavior.
+ Repeated harassment of others. In general, if someone asks you to stop, then stop.
+ When we disagree, try to understand why. Disagreements, both social and technical, happen all the time and OAuthlib is no exception. It is important that we resolve disagreements and differing views constructively. Remember that we're different. The strength of OAuthlib comes from its varied community, people from a wide range of backgrounds. Different people have different perspectives on issues. Being unable to understand why someone holds a viewpoint doesn't mean that they're wrong. Don't forget that it is human to err and blaming each other doesn't get us anywhere. Instead, focus on helping to resolve issues and learning from mistakes.
+
+For reading the original text, please visit the [Django Code of Conduct](https://www.djangoproject.com/conduct/).
diff --git a/LICENSE b/LICENSE
index c10d256..84b5c75 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,4 +1,4 @@
-Copyright (c) 2011 Idan Gazit and contributors
+Copyright (c) 2018 The OAuthlib Community
All rights reserved.
Redistribution and use in source and binary forms, with or without
@@ -24,4 +24,4 @@ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
-OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/README.rst b/README.rst
index 6741a75..a84307f 100644
--- a/README.rst
+++ b/README.rst
@@ -1,5 +1,5 @@
-OAuthLib
-========
+OAuthLib - Python Framework for OAuth1 & OAuth2
+===============================================
*A generic, spec-compliant, thorough implementation of the OAuth request-signing
logic for Python 2.7 and 3.4+.*
@@ -16,6 +16,9 @@ logic for Python 2.7 and 3.4+.*
.. image:: https://img.shields.io/pypi/l/oauthlib.svg
:target: https://pypi.org/project/oauthlib/
:alt: License
+.. image:: https://app.fossa.io/api/projects/git%2Bgithub.com%2Foauthlib%2Foauthlib.svg?type=shield
+ :target: https://app.fossa.io/projects/git%2Bgithub.com%2Foauthlib%2Foauthlib?ref=badge_shield
+ :alt: FOSSA Status
.. image:: https://img.shields.io/readthedocs/oauthlib.svg
:target: https://oauthlib.readthedocs.io/en/latest/index.html
:alt: Read the Docs
@@ -34,7 +37,7 @@ both of the following:
.. _`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
+OAuthLib is a framework which implements the logic of OAuth1 or OAuth2 without
assuming a specific HTTP request object or web framework. Use it to graft OAuth
client support onto your favorite HTTP library, or provide support onto your
favourite web framework. If you're a maintainer of such a library, write a thin
@@ -104,10 +107,22 @@ License
OAuthLib is yours to use and abuse according to the terms of the BSD license.
Check the LICENSE file for full details.
+Credits
+-------
+
+OAuthLib has been started and maintained several years by Idan Gazit and other
+amazing `AUTHORS`_. Thanks to their wonderful work, the open-source `community`_
+creation has been possible and the project can stay active and reactive to users
+requests.
+
+
+.. _`AUTHORS`: https://github.com/oauthlib/oauthlib/blob/master/AUTHORS
+.. _`community`: https://github.com/oauthlib/
+
Changelog
---------
-*OAuthLib is in active development, with the core of both OAuth 1 and 2
+*OAuthLib is in active development, with the core of both OAuth1 and OAuth2
completed, for providers as well as clients.* See `supported features`_ for
details.
diff --git a/docs/conf.py b/docs/conf.py
index 017f686..2594e38 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -41,7 +41,7 @@ master_doc = 'index'
# General information about the project.
project = u'OAuthLib'
-copyright = u'2012, Idan Gazit and the Python Community'
+copyright = u'2018, The OAuthlib Community'
# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the
diff --git a/docs/contributing.rst b/docs/contributing.rst
index 601c567..cbdb519 100644
--- a/docs/contributing.rst
+++ b/docs/contributing.rst
@@ -30,12 +30,37 @@ personal label matching your GitHub ID will be assigned to that issue.
Feel free to propose issues that aren't described!
+oauthlib community rules
+========================
+
+oauthlib is a community of developers which adheres to a very simple set of
+rules.
+
+Code of Conduct
+---------------
+This project adheres to a `Code of Conduct`_ based on Django. As a community
+member you have to read and agree with it.
+
+For more information please contact us and/or visit the original
+`Django Code of Conduct`_ homepage.
+
+.. _`Code of Conduct`: https://github.com/oauthlib/oauthlib/blob/master/CODE_OF_CONDUCT.md
+.. _`Django Code of Conduct`: https://www.djangoproject.com/conduct/
+
+Code of Merit
+-------------
+Please read the community's `Code of Merit`_. Every contributor will know the
+real purpose of their contributions to this project.
+
+.. _`Code of Merit`: http://code-of-merit.org/
+
+
Setting up topic branches and generating pull requests
======================================================
While it's handy to provide useful code snippets in an issue, it is better for
you as a developer to submit pull requests. By submitting pull request your
-contribution to OpenComparison will be recorded by Github.
+contribution to OAuthlib will be recorded by Github.
In git it is best to isolate each topic or feature into a "topic branch". While
individual commits allow you control over how small individual changes are made
@@ -127,7 +152,7 @@ request that fails this test suite will be **rejected**.
Testing multiple versions of Python
-----------------------------------
-OAuthLib supports Python 2.6, 2.7, 3.2, 3.3 and experimentally PyPy. Testing
+OAuthLib supports Python 2.7, 3.4, 3.5, 3.6 and PyPy. Testing
all versions conveniently can be done using `Tox`_.
.. sourcecode:: bash
@@ -150,6 +175,17 @@ version. For Ubuntu you can easily install all after adding one ppa.
.. _`Tox`: https://tox.readthedocs.io/en/latest/install.html
.. _`virtualenv`: https://virtualenv.pypa.io/en/latest/installation/
+Test upstream applications
+-----------------------------------
+
+Remember, OAuthLib is used by several 3rd party projects. If you think you
+submit a breaking change, confirm that other projects builds are not affected.
+
+.. sourcecode:: bash
+
+ $ make
+
+
If you add code you need to add tests!
--------------------------------------
@@ -207,7 +243,7 @@ How pull requests are checked, tested, and done
First we pull the code into a local branch::
- git remote add <submitter-github-name> git@github.com:<submitter-github-name>/opencomparison.git
+ git remote add <submitter-github-name> git@github.com:<submitter-github-name>/oauthlib.git
git fetch <submitter-github-name>
git checkout -b <branch-name> <submitter-github-name>/<branch-name>
diff --git a/docs/faq.rst b/docs/faq.rst
index 38b0e92..d9cd5c6 100644
--- a/docs/faq.rst
+++ b/docs/faq.rst
@@ -14,19 +14,23 @@ What parts of OAuth 1 & 2 are supported?
OAuth 1 with RSA-SHA1 signatures says "could not import cryptography". What should I do?
----------------------------------------------------------------------------------
- Install cryptography via pip.
+ Install oauthlib with rsa flag or install cryptography manually via pip.
.. code-block:: sh
+ $ pip install oauthlib[rsa]
+ ..or..
$ pip install cryptography
OAuth 2 ServiceApplicationClient and OAuth 1 with RSA-SHA1 signatures say "could not import jwt". What should I do?
-------------------------------------------------------------------------------------------------------------------
- Install pyjwt and cryptography with pip.
+ Install oauthlib with signedtoken flag or install pyjwt and cryptography manually with pip.
.. code-block:: sh
+ $ pip install oauthlib[signedtoken]
+ ..or..
$ pip install pyjwt cryptography
What does ValueError `Only unicode objects are escapable. Got one of type X.` mean?
@@ -72,8 +76,8 @@ How do I use OAuthlib as a provider with Django, Flask and other web frameworks?
- 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
+ For other frameworks, please get in touch by opening a `GitHub issue`_ or
+ on `Gitter OAuthLib community`_. If you have written an OAuthLib package that
supports your favorite framework, please open a Pull Request to update the docs.
@@ -101,5 +105,5 @@ Some argue OAuth 2 is worse than 1, is that true?
.. _`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
+.. _`Gitter OAuthLib community`: https://gitter.im/oauthlib/Lobby
.. _`difference`: https://www.cyberciti.biz/faq/authentication-vs-authorization/
diff --git a/docs/oauth1/server.rst b/docs/oauth1/server.rst
index 2a91f30..db469d2 100644
--- a/docs/oauth1/server.rst
+++ b/docs/oauth1/server.rst
@@ -433,9 +433,9 @@ shown below as well as run your flask server locally on port `5000`.
7. Let us know how it went!
---------------------------
-Drop a line in our `G+ community`_ or open a `GitHub issue`_ =)
+Drop a line in our `Gitter OAuthLib community`_ or open a `GitHub issue`_ =)
-.. _`G+ community`: https://plus.google.com/communities/101889017375384052571
+.. _`Gitter OAuthLib community`: https://gitter.im/oauthlib/Lobby
.. _`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/grants/jwt.rst b/docs/oauth2/grants/jwt.rst
index db65342..2c1c0e2 100644
--- a/docs/oauth2/grants/jwt.rst
+++ b/docs/oauth2/grants/jwt.rst
@@ -1,7 +1,14 @@
-==========
-JWT Tokens
-==========
+==============================================================
+JWT Profile for Client Authentication and Authorization Grants
+==============================================================
-Not yet implemented. Track progress in `GitHub issue 50`_.
+If you're looking at JWT Tokens, please see :doc:`Bearer Tokens </oauth2/tokens/bearer>` instead.
-.. _`GitHub issue 50`: https://github.com/oauthlib/oauthlib/issues/50
+The JWT Profile `RFC7523`_ implements the `RFC7521`_ abstract assertion
+protocol. It aims to extend the OAuth2 protocol to use JWT as an
+additional authorization grant.
+
+Currently, this is not implemented but all PRs are welcome. See how to :doc:`Contribute </contributing>`.
+
+.. _`RFC7521`: https://tools.ietf.org/html/rfc7521
+.. _`RFC7523`: https://tools.ietf.org/html/rfc7523
diff --git a/docs/oauth2/preconfigured_servers.rst b/docs/oauth2/preconfigured_servers.rst
index 6184c27..e1f629c 100644
--- a/docs/oauth2/preconfigured_servers.rst
+++ b/docs/oauth2/preconfigured_servers.rst
@@ -12,7 +12,8 @@ Construction is simple, only import your validator and you are good to go::
server = WebApplicationServer(your_validator)
-If you prefer to construct tokens yourself you may pass a token generator::
+If you prefer to construct tokens yourself you may pass a token generator (see
+ :doc:`Tokens <tokens/tokens>` for more examples like JWT) ::
def your_token_generator(request, refresh_token=False):
return 'a_custom_token' + request.client_id
@@ -21,6 +22,9 @@ If you prefer to construct tokens yourself you may pass a token generator::
This function is passed the request object and a boolean indicating whether to generate an access token (False) or a refresh token (True).
+.. autoclass:: oauthlib.oauth2.Server
+ :members:
+
.. autoclass:: oauthlib.oauth2.WebApplicationServer
:members:
diff --git a/docs/oauth2/server.rst b/docs/oauth2/server.rst
index 8f8b77b..35a58aa 100644
--- a/docs/oauth2/server.rst
+++ b/docs/oauth2/server.rst
@@ -493,9 +493,9 @@ at runtime by a function, rather then by a list.
6. Let us know how it went!
---------------------------
-Drop a line in our `G+ community`_ or open a `GitHub issue`_ =)
+Drop a line in our `Gitter OAuthLib community`_ or open a `GitHub issue`_ =)
-.. _`G+ community`: https://plus.google.com/communities/101889017375384052571
+.. _`Gitter OAuthLib community`: https://gitter.im/oauthlib/Lobby
.. _`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/bearer.rst b/docs/oauth2/tokens/bearer.rst
index 8c6270d..0776db8 100644
--- a/docs/oauth2/tokens/bearer.rst
+++ b/docs/oauth2/tokens/bearer.rst
@@ -2,12 +2,122 @@
Bearer Tokens
=============
-The most common OAuth 2 token type. It provides very little in terms of security
-and relies heavily upon the ability of the client to keep the token secret.
+The most common OAuth 2 token type.
-Bearer tokens are the default setting with all configured endpoints. Generally
+Bearer tokens is the default setting for all configured endpoints. Generally
you will not need to ever construct a token yourself as the provided servers
will do so for you.
+By default, :doc:`*Server </oauth2/preconfigured_servers>` generate Bearer tokens as
+random strings. However, you can change the default behavior to generate JWT
+instead. All preconfigured servers take as parameters `token_generator` and
+`refresh_token_generator` to fit your needs.
+
+.. contents:: Tutorial Contents
+ :depth: 3
+
+
+1. Generate signed JWT
+----------------------
+
+A function is available to generate signed JWT (with RS256 PEM key) with static
+and dynamic claims.
+
+.. code-block:: python
+
+ from oauthlib.oauth2.rfc6749 import tokens
+ from oauthlib.oauth2 import Server
+
+ private_pem_key = <load_your_key_in_pem_format>
+ validator = <instantiate_your_validator>
+
+ server = Server(
+ your_validator,
+ token_generator=tokens.signed_token_generator(private_pem_key, issuer="foobar")
+ )
+
+
+Note that you can add any custom claims in `RequestValidator` methods by adding them to
+`request.claims` dictionary. Example below:
+
+
+.. code-block:: python
+
+ def validate_client_id(self, client_id, request):
+ (.. your usual checks ..)
+
+ request.claims = {
+ 'aud': self.client_id
+ }
+ return True
+
+
+Once completed, the token endpoint will generate access_token in JWT form:
+
+.. code-block:: shell
+
+
+ access_token=eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJy(..)&expires_in=120&token_type=Bearer(..)
+
+
+And you will find all claims in its decoded form:
+
+
+.. code-block:: javascript
+
+ {
+ "aud": "<client_id>",
+ "iss": "foobar",
+ "scope": "profile calendar",
+ "exp": 12345,
+ }
+
+
+2. Define your own implementation (text, JWT, JWE, ...)
+----------------------------------------------------------------
+
+Sometime you may want to generate custom `access_token` with a reference from a
+database (as text) or use a HASH signature in JWT or use JWE (encrypted content).
+
+Also, note that you can declare the generate function in your instanciated
+validator to benefit of the `self` variables.
+
+See the example below:
+
+.. code-block:: python
+
+ class YourValidator(RequestValidator):
+ def __init__(self, secret, issuer):
+ self.secret = secret
+ self.issuer = issuer
+
+ def generate_access_token(self, request):
+ token = jwt.encode({
+ "ref": str(libuuid.uuid4()),
+ "aud": request.client_id,
+ "iss": self.issuer,
+ "exp": now + datetime.timedelta(seconds=request.expires_in)
+ }, self.secret, algorithm='HS256').decode()
+ return token
+
+
+Then associate it to your `Server`:
+
+.. code-block:: python
+
+ validator = YourValidator(secret="<your_secret>", issuer="<your_issuer_id>")
+
+ server = Server(
+ your_validator,
+ token_generator=validator.generate_access_token
+ )
+
+
+3. BearerToken API
+------------------
+
+If none of the :doc:`/oauth2/preconfigured_servers` fit your needs, you can
+declare your own Endpoints and use the `BearerToken` API as below.
+
.. autoclass:: oauthlib.oauth2.BearerToken
:members:
diff --git a/docs/oauth2/tokens/tokens.rst b/docs/oauth2/tokens/tokens.rst
index f341509..4e19e7e 100644
--- a/docs/oauth2/tokens/tokens.rst
+++ b/docs/oauth2/tokens/tokens.rst
@@ -3,8 +3,7 @@ Tokens
======
The main token type of OAuth 2 is Bearer tokens and that is what OAuthLib
-currently supports. Other tokens, such as JWT, SAML and possibly MAC (if the
-spec matures) can easily be added (and will be in due time).
+currently supports. Other tokens, such as SAML and MAC can easily be added.
The purpose of a token is to authorize access to protected resources to a client
(i.e. your G+ feed).
diff --git a/docs/release_process.rst b/docs/release_process.rst
index aab97c4..9ee987c 100644
--- a/docs/release_process.rst
+++ b/docs/release_process.rst
@@ -2,12 +2,12 @@ Release process
===============
OAuthLib has got to a point where quite a few libraries and users depend on it.
-Because of this a more careful release procedure will be introduced to make
+Because of this, a more careful release procedure will be introduced to make
sure all these lovely projects don't suddenly break.
When approaching a release we will run the unittests for a set of downstream
libraries using the unreleased version of OAuthLib. If OAuthLib is the cause of
-failing tests we will either
+failing tests we will either:
1. Find a way to introduce the change without breaking downstream. However,
this is not always the best long term option.
@@ -25,7 +25,7 @@ OAuthLib release issue on Github at least 2 days prior to release detailing the
changes and pings the primary contacts for each downstream project. Please
respond within those 2 days if you have major concerns.
-How to get on the notifcations list
+How to get on the notifications list
-----------------------------------
Which projects and the instructions for testing each will be defined in
@@ -45,8 +45,8 @@ A note on versioning
--------------------
Historically OAuthLib has not been very good at semantic versioning but that
-will change after the 1.0.0 release due late 2014. After that poing any major
-digit release (e.g. 2.0.0) may introduce non backwards compatible changes.
+has changed since the 1.0.0 in 2014. Since, any major digit release
+(e.g. 2.0.0) may introduce non backwards compatible changes.
Minor point (1.1.0) releases will introduce non API breaking new features and
changes. Bug releases (1.0.1) will include minor fixes that needs to be
released quickly (e.g. after a bigger release unintentionally introduced a
diff --git a/oauthlib/__init__.py b/oauthlib/__init__.py
index b7586d2..bc5d96b 100644
--- a/oauthlib/__init__.py
+++ b/oauthlib/__init__.py
@@ -5,7 +5,7 @@
A generic, spec-compliant, thorough implementation of the OAuth
request-signing logic.
- :copyright: (c) 2011 by Idan Gazit.
+ :copyright: (c) 2018 by The OAuthlib Community
:license: BSD, see LICENSE for details.
"""
import logging
diff --git a/oauthlib/common.py b/oauthlib/common.py
index f25656f..6364761 100644
--- a/oauthlib/common.py
+++ b/oauthlib/common.py
@@ -114,7 +114,7 @@ def decode_params_utf8(params):
return decoded
-urlencoded = set(always_safe) | set('=&;:%+~,*@!()/?')
+urlencoded = set(always_safe) | set('=&;:%+~,*@!()/?\'$')
def urldecode(query):
@@ -426,7 +426,6 @@ class Request(object):
}
self._params.update(dict(urldecode(self.uri_query)))
self._params.update(dict(self.decoded_body or []))
- self._params.update(self.headers)
def __getattr__(self, name):
if name in self._params:
diff --git a/oauthlib/oauth2/rfc6749/grant_types/authorization_code.py b/oauthlib/oauth2/rfc6749/grant_types/authorization_code.py
index 0660263..ab4c184 100644
--- a/oauthlib/oauth2/rfc6749/grant_types/authorization_code.py
+++ b/oauthlib/oauth2/rfc6749/grant_types/authorization_code.py
@@ -140,7 +140,6 @@ class AuthorizationCodeGrant(GrantTypeBase):
oauthlib.oauth2.BearerToken.
:returns: headers, body, status
:raises: FatalClientError on invalid redirect URI or client id.
- ValueError if scopes are not set on the request object.
A few examples::
@@ -151,12 +150,6 @@ class AuthorizationCodeGrant(GrantTypeBase):
>>> from oauthlib.oauth2 import AuthorizationCodeGrant, BearerToken
>>> token = BearerToken(your_validator)
>>> grant = AuthorizationCodeGrant(your_validator)
- >>> grant.create_authorization_response(request, token)
- Traceback (most recent call last):
- File "<stdin>", line 1, in <module>
- File "oauthlib/oauth2/rfc6749/grant_types.py", line 513, in create_authorization_response
- raise ValueError('Scopes must be set on post auth.')
- ValueError: Scopes must be set on post auth.
>>> request.scopes = ['authorized', 'in', 'some', 'form']
>>> grant.create_authorization_response(request, token)
(u'http://client.com/?error=invalid_request&error_description=Missing+response_type+parameter.', None, None, 400)
@@ -182,11 +175,6 @@ class AuthorizationCodeGrant(GrantTypeBase):
.. _`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
- # post auth use validate_authorization_request
- if not request.scopes:
- raise ValueError('Scopes must be set on post auth.')
-
self.validate_authorization_request(request)
log.debug('Pre resource owner authorization validation ok for %r.',
request)
@@ -312,6 +300,8 @@ class AuthorizationCodeGrant(GrantTypeBase):
log.debug('Using default redirect_uri %s.', request.redirect_uri)
if not request.redirect_uri:
raise errors.MissingRedirectURIError(request=request)
+ if not is_absolute_uri(request.redirect_uri):
+ raise errors.InvalidRedirectURIError(request=request)
# Then check for normal errors.
@@ -420,6 +410,17 @@ class AuthorizationCodeGrant(GrantTypeBase):
# REQUIRED, if the "redirect_uri" parameter was included in the
# authorization request as described in Section 4.1.1, and their
# values MUST be identical.
+ if request.redirect_uri is None:
+ request.using_default_redirect_uri = True
+ request.redirect_uri = self.request_validator.get_default_redirect_uri(
+ request.client_id, request)
+ log.debug('Using default redirect_uri %s.', request.redirect_uri)
+ if not request.redirect_uri:
+ raise errors.MissingRedirectURIError(request=request)
+ else:
+ request.using_default_redirect_uri = False
+ log.debug('Using provided redirect_uri %s', request.redirect_uri)
+
if not self.request_validator.confirm_redirect_uri(request.client_id, request.code,
request.redirect_uri, request.client,
request):
diff --git a/oauthlib/oauth2/rfc6749/grant_types/implicit.py b/oauthlib/oauth2/rfc6749/grant_types/implicit.py
index bdab814..3a5c058 100644
--- a/oauthlib/oauth2/rfc6749/grant_types/implicit.py
+++ b/oauthlib/oauth2/rfc6749/grant_types/implicit.py
@@ -200,11 +200,6 @@ class ImplicitGrant(GrantTypeBase):
.. _`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
- # post auth use validate_authorization_request
- if not request.scopes:
- raise ValueError('Scopes must be set on post auth.')
-
self.validate_token_request(request)
# If the request fails due to a missing, invalid, or mismatching
diff --git a/oauthlib/oauth2/rfc6749/parameters.py b/oauthlib/oauth2/rfc6749/parameters.py
index 9ea8c44..c5127e7 100644
--- a/oauthlib/oauth2/rfc6749/parameters.py
+++ b/oauthlib/oauth2/rfc6749/parameters.py
@@ -279,6 +279,10 @@ def parse_implicit_response(uri, state=None, scope=None):
fragment = urlparse.urlparse(uri).fragment
params = dict(urlparse.parse_qsl(fragment, keep_blank_values=True))
+ for key in ('expires_in',):
+ if key in params: # cast things to int
+ params[key] = int(params[key])
+
if 'scope' in params:
params['scope'] = scope_to_list(params['scope'])
diff --git a/oauthlib/oauth2/rfc6749/request_validator.py b/oauthlib/oauth2/rfc6749/request_validator.py
index bf1515d..ff3bbd6 100644
--- a/oauthlib/oauth2/rfc6749/request_validator.py
+++ b/oauthlib/oauth2/rfc6749/request_validator.py
@@ -346,7 +346,6 @@ class RequestValidator(object):
the claims dict, which should be saved for later use when generating the
id_token and/or UserInfo response content.
- :param client_id: Unicode client identifier
:param token: A Bearer token dict
:param request: The HTTP Request (oauthlib.common.Request)
:rtype: The default redirect URI for the client
diff --git a/setup.py b/setup.py
index 0c4e564..1d69e0d 100755
--- a/setup.py
+++ b/setup.py
@@ -33,7 +33,7 @@ setup(
version=oauthlib.__version__,
description='A generic, spec-compliant, thorough implementation of the OAuth request-signing logic',
long_description=fread('README.rst'),
- author='Idan Gazit',
+ author='The OAuthlib Community',
author_email='idan@gazit.me',
maintainer='Ib Lundgren',
maintainer_email='ib.lundgren@gmail.com',
diff --git a/tests/oauth2/rfc6749/clients/test_mobile_application.py b/tests/oauth2/rfc6749/clients/test_mobile_application.py
index 51e4dab..622b275 100644
--- a/tests/oauth2/rfc6749/clients/test_mobile_application.py
+++ b/tests/oauth2/rfc6749/clients/test_mobile_application.py
@@ -40,7 +40,7 @@ class MobileApplicationClientTest(TestCase):
token = {
"access_token": "2YotnFZFEjr1zCsicMWpAA",
"token_type": "example",
- "expires_in": "3600",
+ "expires_in": 3600,
"expires_at": 4600,
"scope": scope,
"example_parameter": "example_value"
diff --git a/tests/oauth2/rfc6749/endpoints/test_base_endpoint.py b/tests/oauth2/rfc6749/endpoints/test_base_endpoint.py
index 4ad0ed9..4f78d9b 100644
--- a/tests/oauth2/rfc6749/endpoints/test_base_endpoint.py
+++ b/tests/oauth2/rfc6749/endpoints/test_base_endpoint.py
@@ -24,7 +24,9 @@ class BaseEndpointTest(TestCase):
validator = RequestValidator()
server = Server(validator)
server.catch_errors = True
- h, b, s = server.create_authorization_response('https://example.com')
+ h, b, s = server.create_token_response(
+ 'https://example.com?grant_type=authorization_code&code=abc'
+ )
self.assertIn("server_error", b)
self.assertEqual(s, 500)
diff --git a/tests/oauth2/rfc6749/endpoints/test_credentials_preservation.py b/tests/oauth2/rfc6749/endpoints/test_credentials_preservation.py
index 0eb719f..50c2956 100644
--- a/tests/oauth2/rfc6749/endpoints/test_credentials_preservation.py
+++ b/tests/oauth2/rfc6749/endpoints/test_credentials_preservation.py
@@ -116,3 +116,24 @@ class PreservationTest(TestCase):
self.assertRaises(errors.MissingRedirectURIError,
self.mobile.create_authorization_response,
auth_uri + '&response_type=token', scopes=['random'])
+
+ def test_default_uri_in_token(self):
+ auth_uri = 'http://example.com/path?state=xyz&client_id=abc'
+ token_uri = 'http://example.com/path'
+
+ # authorization grant
+ h, _, s = self.web.create_authorization_response(
+ auth_uri + '&response_type=code', scopes=['random'])
+ self.assertEqual(s, 302)
+ self.assertIn('Location', h)
+ self.assertTrue(h['Location'].startswith(self.DEFAULT_REDIRECT_URI))
+
+ # confirm_redirect_uri should return true if the redirect uri
+ # was not given in the authorization AND not in the token request.
+ self.validator.confirm_redirect_uri.return_value = True
+ code = get_query_credentials(h['Location'])['code'][0]
+ self.validator.validate_code.side_effect = self.set_state('xyz')
+ _, body, s = self.web.create_token_response(token_uri,
+ body='grant_type=authorization_code&code=%s' % code)
+ self.assertEqual(s, 200)
+ self.assertEqual(self.validator.confirm_redirect_uri.call_args[0][2], self.DEFAULT_REDIRECT_URI)
diff --git a/tests/oauth2/rfc6749/endpoints/test_error_responses.py b/tests/oauth2/rfc6749/endpoints/test_error_responses.py
index 875b3a5..ef05c4d 100644
--- a/tests/oauth2/rfc6749/endpoints/test_error_responses.py
+++ b/tests/oauth2/rfc6749/endpoints/test_error_responses.py
@@ -44,6 +44,22 @@ class ErrorResponseTest(TestCase):
self.assertRaises(errors.InvalidRedirectURIError,
self.mobile.create_authorization_response, uri.format('token'), scopes=['foo'])
+ def test_invalid_default_redirect_uri(self):
+ uri = 'https://example.com/authorize?response_type={0}&client_id=foo'
+ self.validator.get_default_redirect_uri.return_value = "wrong"
+
+ # Authorization code grant
+ self.assertRaises(errors.InvalidRedirectURIError,
+ self.web.validate_authorization_request, uri.format('code'))
+ self.assertRaises(errors.InvalidRedirectURIError,
+ self.web.create_authorization_response, uri.format('code'), scopes=['foo'])
+
+ # Implicit grant
+ self.assertRaises(errors.InvalidRedirectURIError,
+ self.mobile.validate_authorization_request, uri.format('token'))
+ self.assertRaises(errors.InvalidRedirectURIError,
+ self.mobile.create_authorization_response, uri.format('token'), scopes=['foo'])
+
def test_missing_redirect_uri(self):
uri = 'https://example.com/authorize?response_type={0}&client_id=foo'
@@ -237,6 +253,7 @@ class ErrorResponseTest(TestCase):
def test_access_denied(self):
self.validator.authenticate_client.side_effect = self.set_client
+ self.validator.get_default_redirect_uri.return_value = 'https://i.b/cb'
self.validator.confirm_redirect_uri.return_value = False
token_uri = 'https://i.b/token'
# Authorization code grant
@@ -244,6 +261,15 @@ class ErrorResponseTest(TestCase):
body='grant_type=authorization_code&code=foo')
self.assertEqual('invalid_request', json.loads(body)['error'])
+ def test_access_denied_no_default_redirecturi(self):
+ self.validator.authenticate_client.side_effect = self.set_client
+ self.validator.get_default_redirect_uri.return_value = None
+ token_uri = 'https://i.b/token'
+ # Authorization code grant
+ _, body, _ = self.web.create_token_response(token_uri,
+ body='grant_type=authorization_code&code=foo')
+ self.assertEqual('invalid_request', json.loads(body)['error'])
+
def test_unsupported_response_type(self):
self.validator.get_default_redirect_uri.return_value = 'https://i.b/cb'
diff --git a/tests/oauth2/rfc6749/grant_types/test_authorization_code.py b/tests/oauth2/rfc6749/grant_types/test_authorization_code.py
index 704a254..acb23ac 100644
--- a/tests/oauth2/rfc6749/grant_types/test_authorization_code.py
+++ b/tests/oauth2/rfc6749/grant_types/test_authorization_code.py
@@ -77,6 +77,12 @@ class AuthorizationCodeGrantTest(TestCase):
self.assertTrue(self.mock_validator.validate_response_type.called)
self.assertTrue(self.mock_validator.validate_scopes.called)
+ def test_create_authorization_grant_no_scopes(self):
+ bearer = BearerToken(self.mock_validator)
+ self.request.response_mode = 'query'
+ self.request.scopes = []
+ self.auth.create_authorization_response(self.request, bearer)
+
def test_create_authorization_grant_state(self):
self.request.state = 'abc'
self.request.redirect_uri = None
diff --git a/tests/oauth2/rfc6749/test_parameters.py b/tests/oauth2/rfc6749/test_parameters.py
index 6ba98c0..b211d1e 100644
--- a/tests/oauth2/rfc6749/test_parameters.py
+++ b/tests/oauth2/rfc6749/test_parameters.py
@@ -86,7 +86,7 @@ class ParameterTests(TestCase):
'access_token': '2YotnFZFEjr1zCsicMWpAA',
'state': state,
'token_type': 'example',
- 'expires_in': '3600',
+ 'expires_in': 3600,
'expires_at': 4600,
'scope': ['abc']
}
diff --git a/tests/test_common.py b/tests/test_common.py
index b0ea20d..f239368 100644
--- a/tests/test_common.py
+++ b/tests/test_common.py
@@ -39,6 +39,8 @@ class EncodingTest(TestCase):
self.assertItemsEqual(urldecode('foo=bar@spam'), [('foo', 'bar@spam')])
self.assertItemsEqual(urldecode('foo=bar/baz'), [('foo', 'bar/baz')])
self.assertItemsEqual(urldecode('foo=bar?baz'), [('foo', 'bar?baz')])
+ self.assertItemsEqual(urldecode('foo=bar\'s'), [('foo', 'bar\'s')])
+ self.assertItemsEqual(urldecode('foo=$'), [('foo', '$')])
self.assertRaises(ValueError, urldecode, 'foo bar')
self.assertRaises(ValueError, urldecode, '%R')
self.assertRaises(ValueError, urldecode, '%RA')
@@ -212,6 +214,11 @@ class RequestTest(TestCase):
self.assertNotIn('bar', repr(r))
self.assertIn('<SANITIZED>', repr(r))
+ def test_headers_params(self):
+ r = Request(URI, headers={'token': 'foobar'}, body='token=banana')
+ self.assertEqual(r.headers['token'], 'foobar')
+ self.assertEqual(r.token, 'banana')
+
class CaseInsensitiveDictTest(TestCase):
diff --git a/tox.ini b/tox.ini
index 8f3345e..eac7a1e 100644
--- a/tox.ini
+++ b/tox.ini
@@ -1,10 +1,10 @@
[tox]
-envlist = py27,py34,py35,py36,pypy,docs
+envlist = py27,py34,py35,py36,pypy,docs,readme
[testenv]
deps=
-rrequirements-test.txt
-commands=nosetests --with-coverage --cover-html --cover-html-dir={toxinidir}/htmlcov-{envname} --cover-erase --cover-package=oauthlib -w tests
+commands=nosetests -s --with-coverage --cover-html --cover-html-dir={toxinidir}/htmlcov-{envname} --cover-erase --cover-package=oauthlib -w tests
[testenv:py27]
deps=unittest2
@@ -19,3 +19,12 @@ deps=sphinx
changedir=docs
whitelist_externals=make
commands=make clean html
+
+# tox -e readme to mimick pypi long_description check
+[testenv:readme]
+skipsdist=True
+deps=readme
+whitelist_externals=echo
+commands=
+ python setup.py check -r -s
+ echo setup.py/long description is syntaxly correct