[Python-modules-commits] [django-oauth-toolkit] 01/12: Import django-oauth-toolkit_0.11.0.orig.tar.gz
Michael Fladischer
fladi at moszumanska.debian.org
Wed Dec 7 19:43:48 UTC 2016
This is an automated email from the git hooks/post-receive script.
fladi pushed a commit to branch master
in repository django-oauth-toolkit.
commit acc128fd39dff68579749639a8dbffa64a0e4a78
Author: Michael Fladischer <FladischerMichael at fladi.at>
Date: Wed Dec 7 17:08:51 2016 +0100
Import django-oauth-toolkit_0.11.0.orig.tar.gz
---
.travis.yml | 42 +++---
AUTHORS | 7 +
CONTRIBUTING.rst | 2 +-
README.rst | 29 +++-
docs/advanced_topics.rst | 10 +-
docs/changelog.rst | 11 ++
docs/conf.py | 5 +-
docs/contributing.rst | 4 +-
docs/install.rst | 4 +-
docs/rest-framework/getting_started.rst | 6 +-
docs/rest-framework/permissions.rst | 18 +++
docs/settings.rst | 6 +-
docs/tutorial/tutorial_01.rst | 29 ++--
docs/tutorial/tutorial_02.rst | 42 +++++-
docs/tutorial/tutorial_03.rst | 5 +-
oauth2_provider/__init__.py | 2 +-
oauth2_provider/admin.py | 1 +
oauth2_provider/backends.py | 10 +-
oauth2_provider/compat.py | 32 +----
oauth2_provider/compat_handlers.py | 1 +
oauth2_provider/ext/rest_framework/__init__.py | 2 +
oauth2_provider/ext/rest_framework/permissions.py | 25 +++-
oauth2_provider/management/commands/cleartokens.py | 2 +-
oauth2_provider/middleware.py | 12 +-
.../migrations/0003_auto_20160316_1503.py | 20 +++
.../migrations/0004_auto_20160525_1623.py | 29 ++++
oauth2_provider/models.py | 28 ++--
oauth2_provider/oauth2_validators.py | 103 ++++++++++---
oauth2_provider/settings.py | 1 +
oauth2_provider/south_migrations/0001_initial.py | 159 ---------------------
.../south_migrations/0002_adding_indexes.py | 119 ---------------
...n_skip_authorization__chg_field_accesstoken_.py | 121 ----------------
oauth2_provider/south_migrations/__init__.py | 0
oauth2_provider/templates/404.html | 6 -
.../application_confirm_delete.html | 3 +-
.../oauth2_provider/application_detail.html | 3 +-
.../oauth2_provider/application_form.html | 3 +-
.../oauth2_provider/application_list.html | 3 +-
.../application_registration_form.html | 3 +-
.../oauth2_provider/authorized-tokens.html | 1 -
oauth2_provider/templatetags/__init__.py | 0
oauth2_provider/templatetags/compat.py | 10 --
oauth2_provider/tests/settings.py | 38 ++---
oauth2_provider/tests/test_application_views.py | 5 +-
oauth2_provider/tests/test_auth_backends.py | 6 +-
oauth2_provider/tests/test_authorization_code.py | 42 ++++--
oauth2_provider/tests/test_client_credential.py | 2 +-
oauth2_provider/tests/test_decorators.py | 3 +-
oauth2_provider/tests/test_implicit.py | 9 +-
oauth2_provider/tests/test_mixins.py | 5 -
oauth2_provider/tests/test_models.py | 19 +--
oauth2_provider/tests/test_oauth2_backends.py | 1 -
oauth2_provider/tests/test_oauth2_validators.py | 148 ++++++++++++++++++-
oauth2_provider/tests/test_password.py | 4 +-
oauth2_provider/tests/test_rest_framework.py | 71 +++++++--
oauth2_provider/tests/test_scopes.py | 6 +-
oauth2_provider/tests/test_token_revocation.py | 5 +-
oauth2_provider/tests/test_token_view.py | 3 +-
oauth2_provider/tests/urls.py | 4 +-
oauth2_provider/urls.py | 12 +-
oauth2_provider/views/__init__.py | 1 +
oauth2_provider/views/base.py | 6 +-
requirements/base.txt | 2 +-
requirements/testing.txt | 1 -
runtests.py | 6 +-
setup.cfg | 2 +
setup.py | 2 +-
tox.ini | 31 ++--
68 files changed, 683 insertions(+), 670 deletions(-)
diff --git a/.travis.yml b/.travis.yml
index b344a75..fa77f4a 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,35 +1,35 @@
language: python
-python: "2.7"
+python:
+ - "3.5"
+
sudo: false
env:
- - TOX_ENV=py27-django17
- - TOX_ENV=py27-django18
- - TOX_ENV=py27-django19
- - TOX_ENV=py32-django17
- - TOX_ENV=py32-django18
- - TOX_ENV=py33-django17
- - TOX_ENV=py33-django18
- - TOX_ENV=py34-django17
- - TOX_ENV=py34-django18
- - TOX_ENV=py34-django19
- - TOX_ENV=py35-django18
- - TOX_ENV=py35-django19
- - TOX_ENV=docs
+ - TOXENV=py27-django18
+ - TOXENV=py27-django19
+ - TOXENV=py27-django110
+ - TOXENV=py27-djangomaster
+ - TOXENV=py32-django18
+ - TOXENV=py33-django18
+ - TOXENV=py34-django18
+ - TOXENV=py34-django19
+ - TOXENV=py34-django110
+ - TOXENV=py34-djangomaster
+ - TOXENV=py35-django18
+ - TOXENV=py35-django19
+ - TOXENV=py35-django110
+ - TOXENV=py35-djangomaster
+ - TOXENV=docs
matrix:
- # Python 3.5 not yet available on travis, watch this to see when it is.
fast_finish: true
- allow_failures:
- - env: TOX_ENV=py35-django18
- - env: TOX_ENV=py35-django19
install:
- - pip install tox
+ - pip install tox "virtualenv<14"
- pip install coveralls
script:
- - tox -e $TOX_ENV
+ - tox
-after_success:
+after_script:
- coveralls
diff --git a/AUTHORS b/AUTHORS
index 7b2c6b8..3600835 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -16,3 +16,10 @@ Hiroki Kiyohara
Diego Garcia
Bas van Oostveen
Bart Merenda
+Paul Oswald
+Jens Timmerman
+Jim Graham
+pySilver
+Silvano Cerza
+Federico Dolce
+Alessandro De Angelis
diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst
index 69be21a..61d1327 100644
--- a/CONTRIBUTING.rst
+++ b/CONTRIBUTING.rst
@@ -2,4 +2,4 @@ Contributing
============
Thanks for your interest! We love contributions, so please feel free to fix bugs, improve things, provide documentation. Just `follow the
-guidelines <https://django-oauth-toolkit.readthedocs.org/en/latest/contributing.html>`_ and submit a PR.
+guidelines <https://django-oauth-toolkit.readthedocs.io/en/latest/contributing.html>`_ and submit a PR.
diff --git a/README.rst b/README.rst
index 5eed1ba..2f50522 100644
--- a/README.rst
+++ b/README.rst
@@ -32,7 +32,17 @@ Contributing
------------
We love contributions, so please feel free to fix bugs, improve things, provide documentation. Just `follow the
-guidelines <https://django-oauth-toolkit.readthedocs.org/en/latest/contributing.html>`_ and submit a PR.
+guidelines <https://django-oauth-toolkit.readthedocs.io/en/latest/contributing.html>`_ and submit a PR.
+
+Reporting security issues
+-------------------------
+
+If you believe you've found an issue with security implications, please send a detailed description via email to **security at evonove.it**.
+Mail sent to that address reaches the Django OAuth Toolkit core team, who can solve (or forward) the security issue as soon as possible. After
+our acknowledge, we may decide to open a public discussion in our mailing list or issues tracker.
+
+Once you’ve submitted an issue via email, you should receive a response from the core team within 48 hours, and depending on the action to be
+taken, you may receive further followup emails.
Requirements
------------
@@ -62,15 +72,15 @@ Notice that `oauth2_provider` namespace is mandatory.
.. code-block:: python
- urlpatterns = patterns(
+ urlpatterns = [
...
url(r'^o/', include('oauth2_provider.urls', namespace='oauth2_provider')),
- )
+ ]
Documentation
--------------
-The `full documentation <https://django-oauth-toolkit.readthedocs.org/>`_ is on *Read the Docs*.
+The `full documentation <https://django-oauth-toolkit.readthedocs.io/>`_ is on *Read the Docs*.
License
-------
@@ -87,6 +97,15 @@ Roadmap / Todo list (help wanted)
Changelog
---------
+0.11.0 [2016-12-1]
+~~~~~~~~~~~
+
+* #315: AuthorizationView does not overwrite requests on get
+* #425: Added support for Django 1.10
+* #396: added an IsAuthenticatedOrTokenHasScope Permission
+* #357: Support multiple-user clients by allowing User to be NULL for Applications
+* #389: Reuse refresh tokens if enabled.
+
0.10.0 [2015-12-14]
~~~~~~~~~~~~~~~~~~~
@@ -138,7 +157,7 @@ Changelog
* fixed ``get_application_model`` on Django 1.7
* fixed non rotating refresh tokens
* #137: fixed base template
-* customized ``client_secret`` lenght
+* customized ``client_secret`` length
* #38: create access tokens not bound to a user instance for *client credentials* flow
0.7.2 [2014-07-02]
diff --git a/docs/advanced_topics.rst b/docs/advanced_topics.rst
index 5579e0c..6e1d5ac 100644
--- a/docs/advanced_topics.rst
+++ b/docs/advanced_topics.rst
@@ -43,6 +43,10 @@ Write something like this in your settings module::
OAUTH2_PROVIDER_APPLICATION_MODEL='your_app_name.MyApplication'
+Be aware that, when you intend to swap the application model, you should create and run the
+migration defining the swapped application model prior to setting OAUTH2_PROVIDER_APPLICATION_MODEL.
+You'll run into models.E022 in Core system checks if you don't get the order right.
+
That's all, now Django OAuth Toolkit will use your model wherever an Application instance is needed.
**Notice:** `OAUTH2_PROVIDER_APPLICATION_MODEL` is the only setting variable that is not namespaced, this
@@ -55,9 +59,9 @@ That's all, now Django OAuth Toolkit will use your model wherever an Application
Skip authorization form
=======================
-Depending on the OAuth2 flow in use and the access token policy, users might be prompted for the
-same authorization multiple times: sometimes this is acceptable or even desiderable but other it isn't.
-To control DOT behaviour you can use `approval_prompt` parameter when hitting the authorization endpoint.
+Depending on the OAuth2 flow in use and the access token policy, users might be prompted for the
+same authorization multiple times: sometimes this is acceptable or even desirable but other times it isn't.
+To control DOT behaviour you can use the `approval_prompt` parameter when hitting the authorization endpoint.
Possible values are:
* `force` - users are always prompted for authorization.
diff --git a/docs/changelog.rst b/docs/changelog.rst
index a9a4e5a..2c7cd82 100644
--- a/docs/changelog.rst
+++ b/docs/changelog.rst
@@ -1,6 +1,17 @@
Changelog
=========
+0.11.0 [2016-12-1]
+~~~~~~~~~~~
+
+* #424: Added a ROTATE_REFRESH_TOKEN setting to control whether refresh tokens are reused or not
+* #315: AuthorizationView does not overwrite requests on get
+* #425: Added support for Django 1.10
+* #396: Added an IsAuthenticatedOrTokenHasScope Permission
+* #357: Support multiple-user clients by allowing User to be NULL for Applications
+* #389: Reuse refresh tokens if enabled.
+
+
0.10.0 [2015-12-14]
------------------
diff --git a/docs/conf.py b/docs/conf.py
index 84880a1..d9529ec 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -19,9 +19,12 @@ import sys, os, re
here = os.path.abspath(os.path.dirname(__file__))
sys.path.insert(0, here)
sys.path.insert(0, os.path.dirname(here))
-sys.path.insert(0, os.path.join(os.path.dirname(here), 'example'))
os.environ['DJANGO_SETTINGS_MODULE'] = 'oauth2_provider.tests.settings'
+
+import django
+django.setup()
+
import oauth2_provider
# -- General configuration -----------------------------------------------------
diff --git a/docs/contributing.rst b/docs/contributing.rst
index 5ebf257..6de828b 100644
--- a/docs/contributing.rst
+++ b/docs/contributing.rst
@@ -47,7 +47,7 @@ of the pull request.
Pull upstream changes into your fork regularly
==============================================
-It's a good practice to pull upstream changes from master into your fork on a regular basis, infact if you work on
+It's a good practice to pull upstream changes from master into your fork on a regular basis, in fact if you work on
outdated code and your changes diverge too far from master, the pull request has to be rejected.
To pull in upstream changes::
@@ -85,7 +85,7 @@ Add the tests!
--------------
Whenever you add code, you have to add tests as well. We cannot accept untested code, so unless it is a peculiar
-situation you previously discussed with the core commiters, if your pull request reduces the test coverage it will be
+situation you previously discussed with the core committers, if your pull request reduces the test coverage it will be
**immediately rejected**.
Code conventions matter
diff --git a/docs/install.rst b/docs/install.rst
index adaf95f..60e9d8f 100644
--- a/docs/install.rst
+++ b/docs/install.rst
@@ -19,10 +19,10 @@ If you need an OAuth2 provider you'll want to add the following to your urls.py
.. code-block:: python
- urlpatterns = patterns(
+ urlpatterns = [
...
url(r'^o/', include('oauth2_provider.urls', namespace='oauth2_provider')),
- )
+ ]
Sync your database
------------------
diff --git a/docs/rest-framework/getting_started.rst b/docs/rest-framework/getting_started.rst
index 58d9abb..9fa8f87 100644
--- a/docs/rest-framework/getting_started.rst
+++ b/docs/rest-framework/getting_started.rst
@@ -48,7 +48,7 @@ Here's our project's root `urls.py` module:
.. code-block:: python
- from django.conf.urls import url, patterns, include
+ from django.conf.urls import url, include
from django.contrib.auth.models import User, Group
from django.contrib import admin
admin.autodiscover()
@@ -91,11 +91,11 @@ Here's our project's root `urls.py` module:
# Wire up our API using automatic URL routing.
# Additionally, we include login URLs for the browseable API.
- urlpatterns = patterns('',
+ urlpatterns = [
url(r'^', include(router.urls)),
url(r'^o/', include('oauth2_provider.urls', namespace='oauth2_provider')),
url(r'^admin/', include(admin.site.urls)),
- )
+ ]
Also add the following to your `settings.py` module:
diff --git a/docs/rest-framework/permissions.rst b/docs/rest-framework/permissions.rst
index d22e8f4..629bf50 100644
--- a/docs/rest-framework/permissions.rst
+++ b/docs/rest-framework/permissions.rst
@@ -63,3 +63,21 @@ When the request's method is one of "non safe" methods, the access is allowed on
required_scopes = ['music']
The `required_scopes` attribute is mandatory (you just need inform the resource scope).
+
+
+IsAuthenticatedOrTokenHasScope
+------------------------------
+The `TokenHasResourceScope` permission class allows the access only when the current access token has been authorized for **all** the scopes listed in the `required_scopes` field of the view but according of request's method.
+And also allows access to Authenticated users who are authenticated in django, but were not authenticated trought the OAuth2Authentication class.
+This allows for protection of the api using scopes, but still let's users browse the full browseable API.
+To restrict users to only browse the parts of the browseable API they should be allowed to see, you can combine this wwith the DjangoModelPermission or the DjangoObjectPermission.
+
+For example:
+
+.. code-block:: python
+
+ class SongView(views.APIView):
+ permission_classes = [IsAuthenticatedOrTokenHasScope, DjangoModelPermission]
+ required_scopes = ['music']
+
+The `required_scopes` attribute is mandatory.
diff --git a/docs/settings.rst b/docs/settings.rst
index 4999c1c..ac8cfce 100644
--- a/docs/settings.rst
+++ b/docs/settings.rst
@@ -83,6 +83,7 @@ DEFAULT_SCOPES
A list of scopes that should be returned by default.
This is a subset of the keys of the SCOPES setting.
By default this is set to '__all__' meaning that the whole set of SCOPES will be returned.
+
.. code-block:: python
DEFAULT_SCOPES = ['read', 'write']
@@ -100,8 +101,11 @@ REFRESH_TOKEN_EXPIRE_SECONDS
The number of seconds before a refresh token gets removed from the database by
the ``cleartokens`` management command. Check :ref:`cleartokens` management command for further info.
+ROTATE_REFRESH_TOKEN
+~~~~~~~~~~~~~~~~~~~~
+When is set to `True` (default) a new refresh token is issued to the client when the client refreshes an access token.
+
REQUEST_APPROVAL_PROMPT
~~~~~~~~~~~~~~~~~~~~~~~
Can be ``'force'`` or ``'auto'``.
The strategy used to display the authorization form. Refer to :ref:`skip-auth-form`.
-
diff --git a/docs/tutorial/tutorial_01.rst b/docs/tutorial/tutorial_01.rst
index fdb1c3e..fb11db7 100644
--- a/docs/tutorial/tutorial_01.rst
+++ b/docs/tutorial/tutorial_01.rst
@@ -8,15 +8,15 @@ You want to make your own :term:`Authorization Server` to issue access tokens to
Start Your App
--------------
During this tutorial you will make an XHR POST from a Heroku deployed app to your localhost instance.
-Since the domain that will originate the request (the app on Heroku) is different than the destination domain (your local instance),
-you will need to install the `django-cors-headers <https://github.com/ottoyiu/django-cors-headers>`_ app.
+Since the domain that will originate the request (the app on Heroku) is different from the destination domain (your local instance),
+you will need to install the `django-cors-middleware <https://github.com/zestedesavoir/django-cors-middleware>`_ app.
These "cross-domain" requests are by default forbidden by web browsers unless you use `CORS <http://en.wikipedia.org/wiki/Cross-origin_resource_sharing>`_.
-Create a virtualenv and install `django-oauth-toolkit` and `django-cors-headers`:
+Create a virtualenv and install `django-oauth-toolkit` and `django-cors-middleware`:
::
- pip install django-oauth-toolkit django-cors-headers
+ pip install django-oauth-toolkit django-cors-middleware
Start a Django project, add `oauth2_provider` and `corsheaders` to the installed apps, and enable admin:
@@ -33,12 +33,11 @@ Include the Django OAuth Toolkit urls in your `urls.py`, choosing the urlspace y
.. code-block:: python
- urlpatterns = patterns(
- '',
+ urlpatterns = [
url(r'^admin/', include(admin.site.urls)),
url(r'^o/', include('oauth2_provider.urls', namespace='oauth2_provider')),
# ...
- )
+ ]
Include the CORS middleware in your `settings.py`:
@@ -67,7 +66,7 @@ for details on using login templates.
<input type="hidden" name="next" value="{{ next }}" />
-As a final step, execute migrate command, start the internal server, and login with your credentials.
+As a final step, execute the migrate command, start the internal server, and login with your credentials.
Create an OAuth2 Client Application
-----------------------------------
@@ -78,11 +77,11 @@ the API, subject to approval by its users.
Let's register your application.
Point your browser to http://localhost:8000/o/applications/ and add an Application instance.
-`Client id` and `Client Secret` are automatically generated, you have to provide the rest of the informations:
+`Client id` and `Client Secret` are automatically generated; you have to provide the rest of the informations:
* `User`: the owner of the Application (e.g. a developer, or the currently logged in user.)
- * `Redirect uris`: Applications must register at least one redirection endpoint prior to utilizing the
+ * `Redirect uris`: Applications must register at least one redirection endpoint before using the
authorization endpoint. The :term:`Authorization Server` will deliver the access token to the client only if the client
specifies one of the verified redirection uris. For this tutorial, paste verbatim the value
`http://django-oauth-toolkit.herokuapp.com/consumer/exchange/`
@@ -117,9 +116,9 @@ Authorize the Application
+++++++++++++++++++++++++
When a user clicks the link, she is redirected to your (possibly local) :term:`Authorization Server`.
If you're not logged in, you will be prompted for username and password. This is because the authorization
-page is login protected by django-oauth-toolkit. Login, then you should see the (not so cute) form users can use to give
+page is login protected by django-oauth-toolkit. Login, then you should see the (not so cute) form a user can use to give
her authorization to the client application. Flag the *Allow* checkbox and click *Authorize*, you will be redirected
-again on to the consumer service.
+again to the consumer service.
__ loginTemplate_
@@ -128,7 +127,7 @@ you probably need to `setup your login template correctly`__.
Exchange the token
++++++++++++++++++
-At this point your autorization server redirected the user to a special page on the consumer passing in an
+At this point your authorization server redirected the user to a special page on the consumer passing in an
:term:`Authorization Code`, a special token the consumer will use to obtain the final access token.
This operation is usually done automatically by the client application during the request/response cycle, but we cannot
make a POST request from Heroku to your localhost, so we proceed manually with this step. Fill the form with the
@@ -140,9 +139,9 @@ Refresh the token
+++++++++++++++++
The page showing the access token retrieved from the :term:`Authorization Server` also let you make a POST request to
the server itself to swap the refresh token for another, brand new access token.
-Just fill in the missing form fields and click the Refresh button: if everything goes smooth you will see the access and
+Just fill in the missing form fields and click the Refresh button: if everything goes smoothly you will see the access and
refresh token change their values, otherwise you will likely see an error message.
-When finished playing with your authorization server, take note of both the access and refresh tokens, we will use them
+When you have finished playing with your authorization server, take note of both the access and refresh tokens, we will use them
for the next part of the tutorial.
So let's make an API and protect it with your OAuth2 tokens in the :doc:`part 2 of the tutorial <tutorial_02>`.
diff --git a/docs/tutorial/tutorial_02.rst b/docs/tutorial/tutorial_02.rst
index 98fa083..326fa83 100644
--- a/docs/tutorial/tutorial_02.rst
+++ b/docs/tutorial/tutorial_02.rst
@@ -34,14 +34,44 @@ URL this view will respond to:
.. code-block:: python
+ from django.conf.urls import url
+ import oauth2_provider.views as oauth2_views
+ from django.conf import settings
from .views import ApiEndpoint
- urlpatterns = patterns(
- '',
+ # OAuth2 provider endpoints
+ oauth2_endpoint_views = [
+ url(r'^authorize/$', oauth2_views.AuthorizationView.as_view(), name="authorize"),
+ url(r'^token/$', oauth2_views.TokenView.as_view(), name="token"),
+ url(r'^revoke-token/$', oauth2_views.RevokeTokenView.as_view(), name="revoke-token"),
+ ]
+
+ if settings.DEBUG:
+ # OAuth2 Application Management endpoints
+ oauth2_endpoint_views += [
+ url(r'^applications/$', oauth2_views.ApplicationList.as_view(), name="list"),
+ url(r'^applications/register/$', oauth2_views.ApplicationRegistration.as_view(), name="register"),
+ url(r'^applications/(?P<pk>\d+)/$', oauth2_views.ApplicationDetail.as_view(), name="detail"),
+ url(r'^applications/(?P<pk>\d+)/delete/$', oauth2_views.ApplicationDelete.as_view(), name="delete"),
+ url(r'^applications/(?P<pk>\d+)/update/$', oauth2_views.ApplicationUpdate.as_view(), name="update"),
+ ]
+
+ # OAuth2 Token Management endpoints
+ oauth2_endpoint_views += [
+ url(r'^authorized-tokens/$', oauth2_views.AuthorizedTokensListView.as_view(), name="authorized-token-list"),
+ url(r'^authorized-tokens/(?P<pk>\d+)/delete/$', oauth2_views.AuthorizedTokenDeleteView.as_view(),
+ name="authorized-token-delete"),
+ ]
+
+ urlpatterns = [
+ # OAuth 2 endpoints:
+ url(r'^o/', include(oauth2_endpoint_views, namespace="oauth2_provider")),
+
url(r'^admin/', include(admin.site.urls)),
- url(r'^o/', include('oauth2_provider.urls', namespace='oauth2_provider')), # look ma, I'm a provider!
- url(r'^api/hello', ApiEndpoint.as_view()), # and also a resource server!
- )
+ url(r'^api/hello', ApiEndpoint.as_view()), # an example resource endpoint
+ ]
+
+You will probably want to write your own application views to deal with permissions and access control but the ones packaged with the library can get you started when developing the app.
Since we inherit from `ProtectedResourceView`, we're done and our API is OAuth2 protected - for the sake of the lazy
programmer.
@@ -51,7 +81,7 @@ Testing your API
Time to make requests to your API.
For a quick test, try accessing your app at the url `/api/hello` with your browser
-and verify that it reponds with a `403` (in fact no `HTTP_AUTHORIZATION` header was provided).
+and verify that it responds with a `403` (in fact no `HTTP_AUTHORIZATION` header was provided).
You can test your API with anything that can perform HTTP requests, but for this tutorial you can use the online
`consumer client <http://django-oauth-toolkit.herokuapp.com/consumer/client>`_.
Just fill the form with the URL of the API endpoint (i.e. http://localhost:8000/api/hello if you're on localhost) and
diff --git a/docs/tutorial/tutorial_03.rst b/docs/tutorial/tutorial_03.rst
index 210cc24..d49e286 100644
--- a/docs/tutorial/tutorial_03.rst
+++ b/docs/tutorial/tutorial_03.rst
@@ -65,11 +65,10 @@ To check everything works properly, mount the view above to some url:
.. code-block:: python
- urlpatterns = patterns(
- '',
+ urlpatterns = [
url(r'^secret$', 'my.views.secret_page', name='secret'),
'...',
- )
+ ]
You should have an :term:`Application` registered at this point, if you don't, follow the steps in
the previous tutorials to create one. Obtain an :term:`Access Token`, either following the OAuth2
diff --git a/oauth2_provider/__init__.py b/oauth2_provider/__init__.py
index a13af83..326f4a2 100644
--- a/oauth2_provider/__init__.py
+++ b/oauth2_provider/__init__.py
@@ -1,4 +1,4 @@
-__version__ = '0.10.0'
+__version__ = '0.11.0'
__author__ = "Massimiliano Pippi & Federico Frenguelli"
diff --git a/oauth2_provider/admin.py b/oauth2_provider/admin.py
index d3c764a..20bf1cb 100644
--- a/oauth2_provider/admin.py
+++ b/oauth2_provider/admin.py
@@ -6,6 +6,7 @@ from .models import Grant, AccessToken, RefreshToken, get_application_model
class RawIDAdmin(admin.ModelAdmin):
raw_id_fields = ('user',)
+
Application = get_application_model()
admin.site.register(Application, RawIDAdmin)
diff --git a/oauth2_provider/backends.py b/oauth2_provider/backends.py
index 3578fa0..aa7e1ec 100644
--- a/oauth2_provider/backends.py
+++ b/oauth2_provider/backends.py
@@ -1,6 +1,8 @@
-from .compat import get_user_model
+from django.contrib.auth import get_user_model
+
from .oauth2_backends import get_oauthlib_core
+
UserModel = get_user_model()
OAuthLibCore = get_oauthlib_core()
@@ -10,11 +12,9 @@ class OAuth2Backend(object):
Authenticate against an OAuth2 access token
"""
- def authenticate(self, **credentials):
- request = credentials.get('request')
+ def authenticate(self, request=None, **credentials):
if request is not None:
- oauthlib_core = get_oauthlib_core()
- valid, r = oauthlib_core.verify_request(request, scopes=[])
+ valid, r = OAuthLibCore.verify_request(request, scopes=[])
if valid:
return r.user
return None
diff --git a/oauth2_provider/compat.py b/oauth2_provider/compat.py
index 3fca936..f888850 100644
--- a/oauth2_provider/compat.py
+++ b/oauth2_provider/compat.py
@@ -1,13 +1,10 @@
"""
The `compat` module provides support for backwards compatibility with older
-versions of django and python..
+versions of django and python.
"""
-
+# flake8: noqa
from __future__ import unicode_literals
-import django
-from django.conf import settings
-
# urlparse in python3 has been renamed to urllib.parse
try:
from urlparse import urlparse, parse_qs, parse_qsl, urlunparse
@@ -18,28 +15,3 @@ try:
from urllib import urlencode, unquote_plus
except ImportError:
from urllib.parse import urlencode, unquote_plus
-
-# Django 1.5 add support for custom auth user model
-if django.VERSION >= (1, 5):
- AUTH_USER_MODEL = settings.AUTH_USER_MODEL
-else:
- AUTH_USER_MODEL = 'auth.User'
-
-try:
- from django.contrib.auth import get_user_model
-except ImportError:
- from django.contrib.auth.models import User
- get_user_model = lambda: User
-
-# Django's new application loading system
-try:
- from django.apps import apps
- get_model = apps.get_model
-except ImportError:
- from django.db.models import get_model
-
-# Django 1.5 add the support of context variables for the url template tag
-if django.VERSION >= (1, 5):
- from django.template.defaulttags import url
-else:
- from django.templatetags.future import url
diff --git a/oauth2_provider/compat_handlers.py b/oauth2_provider/compat_handlers.py
index 21859e8..ce95a02 100644
--- a/oauth2_provider/compat_handlers.py
+++ b/oauth2_provider/compat_handlers.py
@@ -1,3 +1,4 @@
+# flake8: noqa
# Django 1.9 drops the NullHandler since Python 2.7 includes it
try:
from logging import NullHandler
diff --git a/oauth2_provider/ext/rest_framework/__init__.py b/oauth2_provider/ext/rest_framework/__init__.py
index 00da0a1..4b82672 100644
--- a/oauth2_provider/ext/rest_framework/__init__.py
+++ b/oauth2_provider/ext/rest_framework/__init__.py
@@ -1,2 +1,4 @@
+# flake8: noqa
from .authentication import OAuth2Authentication
from .permissions import TokenHasScope, TokenHasReadWriteScope, TokenHasResourceScope
+from .permissions import IsAuthenticatedOrTokenHasScope
diff --git a/oauth2_provider/ext/rest_framework/permissions.py b/oauth2_provider/ext/rest_framework/permissions.py
index 559bbbc..71b2ac9 100644
--- a/oauth2_provider/ext/rest_framework/permissions.py
+++ b/oauth2_provider/ext/rest_framework/permissions.py
@@ -2,7 +2,8 @@ import logging
from django.core.exceptions import ImproperlyConfigured
-from rest_framework.permissions import BasePermission
+from rest_framework.permissions import BasePermission, IsAuthenticated
+from .authentication import OAuth2Authentication
from ...settings import oauth2_settings
@@ -29,7 +30,7 @@ class TokenHasScope(BasePermission):
return token.is_valid(required_scopes)
- assert False, ('TokenHasScope requires either the'
+ assert False, ('TokenHasScope requires the'
'`oauth2_provider.rest_framework.OAuth2Authentication` authentication '
'class to be used.')
@@ -84,3 +85,23 @@ class TokenHasResourceScope(TokenHasScope):
]
return required_scopes
+
+
+class IsAuthenticatedOrTokenHasScope(BasePermission):
+ """
+ The user is authenticated using some backend or the token has the right scope
+ This only returns True if the user is authenticated, but not using a token
+ or using a token, and the token has the correct scope.
+
+ This is usefull when combined with the DjangoModelPermissions to allow people browse the browsable api's
+ if they log in using the a non token bassed middleware,
+ and let them access the api's using a rest client with a token
+ """
+ def has_permission(self, request, view):
+ is_authenticated = IsAuthenticated().has_permission(request, view)
+ oauth2authenticated = False
+ if is_authenticated:
+ oauth2authenticated = isinstance(request.successful_authenticator, OAuth2Authentication)
+
+ token_has_scope = TokenHasScope()
+ return (is_authenticated and not oauth2authenticated) or token_has_scope.has_permission(request, view)
diff --git a/oauth2_provider/management/commands/cleartokens.py b/oauth2_provider/management/commands/cleartokens.py
index 5b56d2b..48f70b8 100644
--- a/oauth2_provider/management/commands/cleartokens.py
+++ b/oauth2_provider/management/commands/cleartokens.py
@@ -1,4 +1,4 @@
-from django.core.management.base import BaseCommand, CommandError
+from django.core.management.base import BaseCommand
from ...models import clear_expired
diff --git a/oauth2_provider/middleware.py b/oauth2_provider/middleware.py
index 33eab12..3ea729a 100644
--- a/oauth2_provider/middleware.py
+++ b/oauth2_provider/middleware.py
@@ -1,8 +1,18 @@
from django.contrib.auth import authenticate
from django.utils.cache import patch_vary_headers
+# bastb Django 1.10 has updated Middleware. This code imports the Mixin required to get old-style
+# middleware working again
+# More?
+# https://docs.djangoproject.com/en/1.10/topics/http/middleware/#upgrading-pre-django-1-10-style-middleware
+try:
+ from django.utils.deprecation import MiddlewareMixin
+ middleware_parent_class = MiddlewareMixin
+except ImportError:
+ middleware_parent_class = object
-class OAuth2TokenMiddleware(object):
+
+class OAuth2TokenMiddleware(middleware_parent_class):
"""
Middleware for OAuth2 user authentication
diff --git a/oauth2_provider/migrations/0003_auto_20160316_1503.py b/oauth2_provider/migrations/0003_auto_20160316_1503.py
new file mode 100644
index 0000000..5dd05dd
--- /dev/null
+++ b/oauth2_provider/migrations/0003_auto_20160316_1503.py
@@ -0,0 +1,20 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+from django.conf import settings
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('oauth2_provider', '0002_08_updates'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='application',
+ name='user',
+ field=models.ForeignKey(related_name='oauth2_provider_application', blank=True, to=settings.AUTH_USER_MODEL, null=True),
+ ),
+ ]
diff --git a/oauth2_provider/migrations/0004_auto_20160525_1623.py b/oauth2_provider/migrations/0004_auto_20160525_1623.py
new file mode 100644
index 0000000..5ada5db
--- /dev/null
+++ b/oauth2_provider/migrations/0004_auto_20160525_1623.py
@@ -0,0 +1,29 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('oauth2_provider', '0003_auto_20160316_1503'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='accesstoken',
+ name='token',
+ field=models.CharField(unique=True, max_length=255),
+ ),
+ migrations.AlterField(
+ model_name='grant',
+ name='code',
+ field=models.CharField(unique=True, max_length=255),
+ ),
+ migrations.AlterField(
+ model_name='refreshtoken',
+ name='token',
+ field=models.CharField(unique=True, max_length=255),
+ ),
+ ]
diff --git a/oauth2_provider/models.py b/oauth2_provider/models.py
index fd3cdf4..0025fcc 100644
--- a/oauth2_provider/models.py
+++ b/oauth2_provider/models.py
@@ -2,6 +2,8 @@ from __future__ import unicode_literals
from datetime import timedelta
+from django.apps import apps
+from django.conf import settings
from django.core.urlresolvers import reverse
from django.db import models, transaction
from django.utils import timezone
@@ -11,7 +13,7 @@ from django.utils.encoding import python_2_unicode_compatible
from django.core.exceptions import ImproperlyConfigured
from .settings import oauth2_settings
-from .compat import AUTH_USER_MODEL, parse_qsl, urlparse, get_model
+from .compat import parse_qsl, urlparse
from .generators import generate_client_secret, generate_client_id
from .validators import validate_uris
@@ -57,7 +59,9 @@ class AbstractApplication(models.Model):
client_id = models.CharField(max_length=100, unique=True,
default=generate_client_id, db_index=True)
- user = models.ForeignKey(AUTH_USER_MODEL, related_name="%(app_label)s_%(class)s")
+ user = models.ForeignKey(settings.AUTH_USER_MODEL, related_name="%(app_label)s_%(class)s",
+ null=True, blank=True)
+
help_text = _("Allowed URIs list, space separated")
redirect_uris = models.TextField(help_text=help_text,
validators=[validate_uris], blank=True)
@@ -124,10 +128,8 @@ class AbstractApplication(models.Model):
class Application(AbstractApplication):
- pass
-
-# Add swappable like this to not break django 1.4 compatibility
-Application._meta.swappable = 'OAUTH2_PROVIDER_APPLICATION_MODEL'
+ class Meta(AbstractApplication.Meta):
+ swappable = 'OAUTH2_PROVIDER_APPLICATION_MODEL'
@python_2_unicode_compatible
@@ -146,8 +148,8 @@ class Grant(models.Model):
* :attr:`redirect_uri` Self explained
* :attr:`scope` Required scopes, optional
"""
- user = models.ForeignKey(AUTH_USER_MODEL)
- code = models.CharField(max_length=255, db_index=True) # code comes from oauthlib
+ user = models.ForeignKey(settings.AUTH_USER_MODEL)
+ code = models.CharField(max_length=255, unique=True) # code comes from oauthlib
application = models.ForeignKey(oauth2_settings.APPLICATION_MODEL)
expires = models.DateTimeField()
redirect_uri = models.CharField(max_length=255)
@@ -183,8 +185,8 @@ class AccessToken(models.Model):
* :attr:`expires` Date and time of token expiration, in DateTime format
* :attr:`scope` Allowed scopes
"""
- user = models.ForeignKey(AUTH_USER_MODEL, blank=True, null=True)
- token = models.CharField(max_length=255, db_index=True)
+ user = models.ForeignKey(settings.AUTH_USER_MODEL, blank=True, null=True)
+ token = models.CharField(max_length=255, unique=True)
application = models.ForeignKey(oauth2_settings.APPLICATION_MODEL)
expires = models.DateTimeField()
scope = models.TextField(blank=True)
@@ -252,8 +254,8 @@ class RefreshToken(models.Model):
* :attr:`access_token` AccessToken instance this refresh token is
bounded to
"""
- user = models.ForeignKey(AUTH_USER_MODEL)
- token = models.CharField(max_length=255, db_index=True)
+ user = models.ForeignKey(settings.AUTH_USER_MODEL)
+ token = models.CharField(max_length=255, unique=True)
application = models.ForeignKey(oauth2_settings.APPLICATION_MODEL)
access_token = models.OneToOneField(AccessToken,
related_name='refresh_token')
@@ -276,7 +278,7 @@ def get_application_model():
except ValueError:
e = "APPLICATION_MODEL must be of the form 'app_label.model_name'"
raise ImproperlyConfigured(e)
- app_model = get_model(app_label, model_name)
+ app_model = apps.get_model(app_label, model_name)
if app_model is None:
e = "APPLICATION_MODEL refers to model {0} that has not been installed"
raise ImproperlyConfigured(e.format(oauth2_settings.APPLICATION_MODEL))
diff --git a/oauth2_provider/oauth2_validators.py b/oauth2_provider/oauth2_validators.py
index 25908d9..1fd80bb 100644
--- a/oauth2_provider/oauth2_validators.py
+++ b/oauth2_provider/oauth2_validators.py
@@ -7,11 +7,14 @@ import logging
from datetime import timedelta
from django.utils import timezone
+from django.conf import settings
from django.contrib.auth import authenticate
from django.core.exceptions import ObjectDoesNotExist
+from django.db import transaction
from oauthlib.oauth2 import RequestValidator
from .compat import unquote_plus
+from .exceptions import FatalClientError
from .models import Grant, AccessToken, RefreshToken, get_application_model, AbstractApplication
from .settings import oauth2_settings
@@ -57,7 +60,7 @@ class OAuth2Validator(RequestValidator):
return False
try:
- encoding = request.encoding
+ encoding = request.encoding or settings.DEFAULT_CHARSET or 'utf-8'
except AttributeError:
encoding = 'utf-8'
@@ -85,6 +88,9 @@ class OAuth2Validator(RequestValidator):
if self._load_application(client_id, request) is None:
log.debug("Failed basic auth: Application %s does not exist" % client_id)
return False
+ elif request.client.client_id != client_id:
+ log.debug("Failed basic auth: wrong client id %s" % client_id)
+ return False
elif request.client.client_secret != client_secret:
log.debug("Failed basic auth: wrong client secret %s" % client_secret)
return False
@@ -291,41 +297,94 @@ class OAuth2Validator(RequestValidator):
scope=' '.join(request.scopes))
g.save()
+ def rotate_refresh_token(self, request):
+ """
+ Checks if rotate refresh token is enabled
+ """
+ return oauth2_settings.ROTATE_REFRESH_TOKEN
+
+ @transaction.atomic
def save_bearer_token(self, token, request, *args, **kwargs):
"""
- Save access and refresh token, If refresh token is issued, remove old refresh tokens as
- in rfc:`6`
+ Save access and refresh token, If refresh token is issued, remove or
+ reuse old refresh token as in rfc:`6`
+
+ @see: https://tools.ietf.org/html/draft-ietf-oauth-v2-31#page-43
"""
- if request.refresh_token:
- # remove used refresh token
- try:
- RefreshToken.objects.get(token=request.refresh_token).revoke()
- except RefreshToken.DoesNotExist:
... 1570 lines suppressed ...
--
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/python-modules/packages/django-oauth-toolkit.git
More information about the Python-modules-commits
mailing list