[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