[Python-modules-commits] [django-oauth-toolkit] 03/09: Import django-oauth-toolkit_0.10.0.orig.tar.gz
Michael Fladischer
fladi at moszumanska.debian.org
Fri Dec 25 13:53:54 UTC 2015
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 1f492d07e26344781bce98475d85d8928d8b2341
Author: Michael Fladischer <FladischerMichael at fladi.at>
Date: Fri Dec 25 14:06:24 2015 +0100
Import django-oauth-toolkit_0.10.0.orig.tar.gz
---
.gitignore | 1 +
.travis.yml | 24 +--
AUTHORS | 3 +
README.rst | 20 ++-
docs/changelog.rst | 20 ++-
docs/index.rst | 5 +-
docs/install.rst | 1 -
docs/management_commands.rst | 20 +++
docs/rest-framework/permissions.rst | 15 ++
docs/settings.rst | 21 ++-
docs/tutorial/tutorial_01.rst | 22 +--
docs/tutorial/tutorial_03.rst | 6 +-
docs/tutorial/tutorial_04.rst | 2 +-
docs/views/token.rst | 15 ++
docs/views/views.rst | 1 +
oauth2_provider/__init__.py | 4 +-
oauth2_provider/apps.py | 6 +
oauth2_provider/compat.py | 6 +
oauth2_provider/compat_handlers.py | 5 +
oauth2_provider/ext/rest_framework/__init__.py | 2 +-
oauth2_provider/ext/rest_framework/permissions.py | 25 +++
oauth2_provider/forms.py | 13 +-
oauth2_provider/management/__init__.py | 0
oauth2_provider/management/commands/__init__.py | 0
oauth2_provider/management/commands/cleartokens.py | 9 ++
oauth2_provider/migrations/0001_initial.py | 1 +
oauth2_provider/models.py | 38 ++++-
oauth2_provider/oauth2_backends.py | 21 ++-
oauth2_provider/oauth2_validators.py | 8 +-
oauth2_provider/settings.py | 20 ++-
.../application_confirm_delete.html | 2 +-
.../oauth2_provider/application_detail.html | 2 +-
.../oauth2_provider/application_form.html | 2 +-
.../oauth2_provider/application_list.html | 2 +-
.../application_registration_form.html | 2 +-
.../oauth2_provider/authorized-token-delete.html | 9 ++
.../oauth2_provider/authorized-tokens.html | 24 +++
oauth2_provider/templatetags/__init__.py | 0
oauth2_provider/templatetags/compat.py | 10 ++
oauth2_provider/tests/settings.py | 2 +-
oauth2_provider/tests/test_application_views.py | 20 +++
oauth2_provider/tests/test_auth_backends.py | 2 +-
oauth2_provider/tests/test_authorization_code.py | 32 ++++
oauth2_provider/tests/test_client_credential.py | 1 +
oauth2_provider/tests/test_implicit.py | 21 +++
oauth2_provider/tests/test_models.py | 40 +++++
oauth2_provider/tests/test_oauth2_backends.py | 35 ++++
oauth2_provider/tests/test_password.py | 1 +
oauth2_provider/tests/test_rest_framework.py | 51 +++++-
oauth2_provider/tests/test_scopes.py | 1 +
oauth2_provider/tests/test_token_view.py | 178 +++++++++++++++++++++
oauth2_provider/tests/urls.py | 5 +-
oauth2_provider/urls.py | 14 +-
oauth2_provider/views/__init__.py | 1 +
oauth2_provider/views/application.py | 13 +-
oauth2_provider/views/base.py | 8 +-
oauth2_provider/views/generic.py | 4 +-
oauth2_provider/views/token.py | 36 +++++
requirements/base.txt | 7 +-
requirements/optional.txt | 2 +-
requirements/project.txt | 2 +-
requirements/testing.txt | 7 +-
runtests.py | 10 +-
setup.py | 14 +-
tox.ini | 124 ++------------
65 files changed, 806 insertions(+), 212 deletions(-)
diff --git a/.gitignore b/.gitignore
index 227bc50..bf1a049 100644
--- a/.gitignore
+++ b/.gitignore
@@ -25,6 +25,7 @@ __pycache__
pip-log.txt
# Unit test / coverage reports
+.cache
.coverage
.tox
nosetests.xml
diff --git a/.travis.yml b/.travis.yml
index bfbfb94..b344a75 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,25 +1,29 @@
language: python
python: "2.7"
+sudo: false
env:
- - TOX_ENV=py26-django14
- - TOX_ENV=py26-django15
- - TOX_ENV=py26-django16
- - TOX_ENV=py27-django14
- - TOX_ENV=py27-django15
- - TOX_ENV=py27-django16
- TOX_ENV=py27-django17
- TOX_ENV=py27-django18
- - TOX_ENV=py33-django15
- - TOX_ENV=py33-django16
+ - TOX_ENV=py27-django19
+ - TOX_ENV=py32-django17
+ - TOX_ENV=py32-django18
- TOX_ENV=py33-django17
- TOX_ENV=py33-django18
- - TOX_ENV=py34-django15
- - TOX_ENV=py34-django16
- TOX_ENV=py34-django17
- TOX_ENV=py34-django18
+ - TOX_ENV=py34-django19
+ - TOX_ENV=py35-django18
+ - TOX_ENV=py35-django19
- TOX_ENV=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 coveralls
diff --git a/AUTHORS b/AUTHORS
index 8fa585f..7b2c6b8 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -13,3 +13,6 @@ David Fischer
Ash Christopher
Rodney Richardson
Hiroki Kiyohara
+Diego Garcia
+Bas van Oostveen
+Bart Merenda
diff --git a/README.rst b/README.rst
index 42dec11..5eed1ba 100644
--- a/README.rst
+++ b/README.rst
@@ -37,8 +37,8 @@ guidelines <https://django-oauth-toolkit.readthedocs.org/en/latest/contributing.
Requirements
------------
-* Python 2.6, 2.7, 3.3, 3.4
-* Django 1.4, 1.5, 1.6, 1.7, 1.8
+* Python 2.7, 3.2, 3.3, 3.4, 3.5
+* Django 1.7, 1.8, 1.9
Installation
------------
@@ -87,6 +87,22 @@ Roadmap / Todo list (help wanted)
Changelog
---------
+0.10.0 [2015-12-14]
+~~~~~~~~~~~~~~~~~~~
+
+* **#322: dropping support for python 2.6 and django 1.4, 1.5, 1.6**
+* #310: Fixed error that could occur sometimes when checking validity of incomplete AccessToken/Grant
+* #333: Added possibility to specify the default list of scopes returned when scope parameter is missing
+* #325: Added management views of issued tokens
+* #249: Added a command to clean expired tokens
+* #323: Application registration view uses custom application model in form class
+* #299: 'server_class' is now pluggable through Django settings
+* #309: Add the py35-django19 env to travis
+* #308: Use compact syntax for tox envs
+* #306: Django 1.9 compatibility
+* #288: Put additional information when generating token responses
+* #297: Fixed doc about SessionAuthenticationMiddleware
+* #273: Generic read write scope by resource
0.9.0 [2015-07-28]
~~~~~~~~~~~~~~~~~~
diff --git a/docs/changelog.rst b/docs/changelog.rst
index 839a34e..a9a4e5a 100644
--- a/docs/changelog.rst
+++ b/docs/changelog.rst
@@ -1,8 +1,26 @@
Changelog
=========
+0.10.0 [2015-12-14]
+------------------
+
+* **#322: dropping support for python 2.6 and django 1.4, 1.5, 1.6**
+* #310: Fixed error that could occur sometimes when checking validity of incomplete AccessToken/Grant
+* #333: Added possibility to specify the default list of scopes returned when scope parameter is missing
+* #325: Added management views of issued tokens
+* #249: Added a command to clean expired tokens
+* #323: Application registration view uses custom application model in form class
+* #299: 'server_class' is now pluggable through Django settings
+* #309: Add the py35-django19 env to travis
+* #308: Use compact syntax for tox envs
+* #306: Django 1.9 compatibility
+* #288: Put additional information when generating token responses
+* #297: Fixed doc about SessionAuthenticationMiddleware
+* #273: Generic read write scope by resource
+
+
0.9.0 [2015-07-28]
-~~~~~~~~~~~~~~~~~~
+------------------
* ``oauthlib_backend_class`` is now pluggable through Django settings
* #127: ``application/json`` Content-Type is now supported using ``JSONOAuthLibCore``
diff --git a/docs/index.rst b/docs/index.rst
index 4aa62ff..de2c0f8 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -21,8 +21,8 @@ If you need support please send a message to the `Django OAuth Toolkit Google Gr
Requirements
------------
-* Python 2.6, 2.7, 3.3, 3.4
-* Django 1.4, 1.5, 1.6, 1.7
+* Python 2.7, 3.2, 3.3, 3.4, 3.5
+* Django 1.7, 1.8, 1.9
Index
=====
@@ -38,6 +38,7 @@ Index
models
advanced_topics
settings
+ management_commands
glossary
.. toctree::
diff --git a/docs/install.rst b/docs/install.rst
index efc21e9..adaf95f 100644
--- a/docs/install.rst
+++ b/docs/install.rst
@@ -29,7 +29,6 @@ Sync your database
.. sourcecode:: sh
- $ python manage.py syncdb
$ python manage.py migrate oauth2_provider
Next step is our :doc:`first tutorial <tutorial/tutorial_01>`.
diff --git a/docs/management_commands.rst b/docs/management_commands.rst
new file mode 100644
index 0000000..3930062
--- /dev/null
+++ b/docs/management_commands.rst
@@ -0,0 +1,20 @@
+Management commands
+===================
+
+Django OAuth Toolkit exposes some useful management commands that can be run via shell or by other means (eg: cron)
+
+.. _cleartokens:
+
+cleartokens
+~~~~~~~~~~~
+
+The ``cleartokens`` management command allows the user to remove those refresh tokens whose lifetime is greater than the
+amount specified by ``REFRESH_TOKEN_EXPIRE_SECONDS`` settings. It is important that this command is run regularly
+(eg: via cron) to avoid cluttering the database with expired refresh tokens.
+
+If ``cleartokens`` runs daily the maximum delay before a refresh token is
+removed is ``REFRESH_TOKEN_EXPIRE_SECONDS`` + 1 day. This is normally not a
+problem since refresh tokens are long lived.
+
+Note: Refresh tokens need to expire before AccessTokens can be removed from the
+database. Using ``cleartokens`` without ``REFRESH_TOKEN_EXPIRE_SECONDS`` has limited effect.
diff --git a/docs/rest-framework/permissions.rst b/docs/rest-framework/permissions.rst
index 092d580..d22e8f4 100644
--- a/docs/rest-framework/permissions.rst
+++ b/docs/rest-framework/permissions.rst
@@ -48,3 +48,18 @@ For example:
When a request is performed both the `READ_SCOPE` \\ `WRITE_SCOPE` and 'music' scopes are required to be authorized for the current access token.
+TokenHasResourceScope
+----------------------
+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.
+
+When the current request's method is one of the "safe" methods, the access is allowed only if the access token has been authorized for the `scope:read` scope (for example `music:read`).
+When the request's method is one of "non safe" methods, the access is allowed only if the access token has been authorizes for the `scope:write` scope (for example `music:write`).
+
+.. code-block:: python
+
+ class SongView(views.APIView):
+ authentication_classes = [OAuth2Authentication]
+ permission_classes = [TokenHasResourceScope]
+ required_scopes = ['music']
+
+The `required_scopes` attribute is mandatory (you just need inform the resource scope).
diff --git a/docs/settings.rst b/docs/settings.rst
index 6fc46da..4999c1c 100644
--- a/docs/settings.rst
+++ b/docs/settings.rst
@@ -59,6 +59,11 @@ CLIENT_SECRET_GENERATOR_LENGTH
The length of the generated secrets, in characters. If this value is too low,
secrets may become subject to bruteforce guessing.
+OAUTH2_SERVER_CLASS
+~~~~~~~~~~~~~~~~~~~~
+The import string for the ``server_class`` (or ``oauthlib.oauth2.Server`` subclass)
+used in the ``OAuthLibMixin`` that implements OAuth2 grant types.
+
OAUTH2_VALIDATOR_CLASS
~~~~~~~~~~~~~~~~~~~~~~
The import string of the ``oauthlib.oauth2.RequestValidator`` subclass that
@@ -71,7 +76,16 @@ to get a ``Server`` instance.
SCOPES
~~~~~~
-A dictionnary mapping each scope name to its human description.
+A dictionary mapping each scope name to its human description.
+
+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']
READ_SCOPE
~~~~~~~~~~
@@ -81,6 +95,11 @@ WRITE_SCOPE
~~~~~~~~~~~
The name of the *write* scope.
+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.
+
REQUEST_APPROVAL_PROMPT
~~~~~~~~~~~~~~~~~~~~~~~
Can be ``'force'`` or ``'auto'``.
diff --git a/docs/tutorial/tutorial_01.rst b/docs/tutorial/tutorial_01.rst
index c7c6687..fdb1c3e 100644
--- a/docs/tutorial/tutorial_01.rst
+++ b/docs/tutorial/tutorial_01.rst
@@ -8,7 +8,7 @@ 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),
+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.
These "cross-domain" requests are by default forbidden by web browsers unless you use `CORS <http://en.wikipedia.org/wiki/Cross-origin_resource_sharing>`_.
@@ -60,29 +60,29 @@ Allow CORS requests from all domains (just for the scope of this tutorial):
Include the required hidden input in your login template, `registration/login.html`.
The ``{{ next }}`` template context variable will be populated with the correct
-redirect value. See the `Django documentation <https://docs.djangoproject.com/en/dev/topics/auth/default/#django.contrib.auth.views.login>`_
+redirect value. See the `Django documentation <https://docs.djangoproject.com/en/dev/topics/auth/default/#django.contrib.auth.views.login>`_
for details on using login templates.
.. code-block:: html
<input type="hidden" name="next" value="{{ next }}" />
-As a final step, execute syncdb, start the internal server, and login with your credentials.
+As a final step, execute migrate command, start the internal server, and login with your credentials.
Create an OAuth2 Client Application
-----------------------------------
-Before your :term:`Application` can use the :term:`Authorization Server` for user login,
-you must first register the app (also known as the :term:`Client`.) Once registered, your app will be granted access to
+Before your :term:`Application` can use the :term:`Authorization Server` for user login,
+you must first register the app (also known as the :term:`Client`.) Once registered, your app will be granted access to
the API, subject to approval by its users.
-Let's register your application.
+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:
* `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 prior to utilizing 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/`
@@ -101,16 +101,16 @@ process we'll explain shortly)
Test Your Authorization Server
------------------------------
Your authorization server is ready and can begin issuing access tokens. To test the process you need an OAuth2
-consumer; if you are familiar enough with OAuth2, you can use curl, requests, or anything that speaks http. For the rest
-of us, there is a `consumer service <http://django-oauth-toolkit.herokuapp.com/consumer/>`_ deployed on Heroku to test
+consumer; if you are familiar enough with OAuth2, you can use curl, requests, or anything that speaks http. For the rest
+of us, there is a `consumer service <http://django-oauth-toolkit.herokuapp.com/consumer/>`_ deployed on Heroku to test
your provider.
Build an Authorization Link for Your Users
++++++++++++++++++++++++++++++++++++++++++
Authorizing an application to access OAuth2 protected data in an :term:`Authorization Code` flow is always initiated
-by the user. Your application can prompt users to click a special link to start the process. Go to the
+by the user. Your application can prompt users to click a special link to start the process. Go to the
`Consumer <http://django-oauth-toolkit.herokuapp.com/consumer/>`_ page and complete the form by filling in your
-application's details obtained from the steps in this tutorial. Submit the form, and you'll receive a link your users can
+application's details obtained from the steps in this tutorial. Submit the form, and you'll receive a link your users can
use to access the authorization page.
Authorize the Application
diff --git a/docs/tutorial/tutorial_03.rst b/docs/tutorial/tutorial_03.rst
index 612414c..210cc24 100644
--- a/docs/tutorial/tutorial_03.rst
+++ b/docs/tutorial/tutorial_03.rst
@@ -24,7 +24,8 @@ which takes care of token verification. In your settings.py:
MIDDLEWARE_CLASSES = (
'...',
- # be sure following two appear in this order
+ # If you use SessionAuthenticationMiddleware, be sure it appears before OAuth2TokenMiddleware.
+ # SessionAuthenticationMiddleware is NOT required for using django-oauth-toolkit.
'django.contrib.auth.middleware.SessionAuthenticationMiddleware',
'oauth2_provider.middleware.OAuth2TokenMiddleware',
'...',
@@ -43,6 +44,9 @@ not used at all, it will try to authenticate user with the OAuth2 access token a
`request.user` and `request._cached_user` fields so that AuthenticationMiddleware (when active)
will not try to get user from the session.
+If you use SessionAuthenticationMiddleware, be sure it appears before OAuth2TokenMiddleware.
+However SessionAuthenticationMiddleware is NOT required for using django-oauth-toolkit.
+
Protect your view
-----------------
The authentication backend will run smoothly with, for example, `login_required` decorators, so
diff --git a/docs/tutorial/tutorial_04.rst b/docs/tutorial/tutorial_04.rst
index e062c1a..e115f82 100644
--- a/docs/tutorial/tutorial_04.rst
+++ b/docs/tutorial/tutorial_04.rst
@@ -18,7 +18,7 @@ Note that these revocation-specific parameters are in addition to the authentica
Setup a Request
----------------
-Depending on the client type you're using, the token revocation request you may submit to the authentication server mayy vary. A `Public` client, for example, will not have access to your `Client Secret`. A revoke request from a public client would omit that secret, and take the form:
+Depending on the client type you're using, the token revocation request you may submit to the authentication server may vary. A `Public` client, for example, will not have access to your `Client Secret`. A revoke request from a public client would omit that secret, and take the form:
::
diff --git a/docs/views/token.rst b/docs/views/token.rst
new file mode 100644
index 0000000..02f6bf5
--- /dev/null
+++ b/docs/views/token.rst
@@ -0,0 +1,15 @@
+Granted Tokens Views
+====================
+
+A set of views is provided to let users handle tokens that have been granted to them, without needing to accessing Django Admin Site.
+Every view provides access only to the tokens that have been granted to the user performing the request.
+
+
+Granted Token views are listed at the url `authorized_tokens/`.
+
+
+For each granted token there is a delete view that allows you to delete such token. You can override default templates `authorized-tokens.html` for the list view and `authorized-token-delete.html` for the delete view; they are located inside `templates/oauth2_provider` folder.
+
+
+.. automodule:: oauth2_provider.views.token
+ :members:
diff --git a/docs/views/views.rst b/docs/views/views.rst
index 34afef9..262f9d2 100644
--- a/docs/views/views.rst
+++ b/docs/views/views.rst
@@ -9,4 +9,5 @@ Django OAuth Toolkit provides a set of pre-defined views for different purposes:
function_based
class_based
application
+ token
mixins
diff --git a/oauth2_provider/__init__.py b/oauth2_provider/__init__.py
index 9ffed34..a13af83 100644
--- a/oauth2_provider/__init__.py
+++ b/oauth2_provider/__init__.py
@@ -1,5 +1,7 @@
-__version__ = '0.9.0'
+__version__ = '0.10.0'
__author__ = "Massimiliano Pippi & Federico Frenguelli"
+default_app_config = 'oauth2_provider.apps.DOTConfig'
+
VERSION = __version__ # synonym
diff --git a/oauth2_provider/apps.py b/oauth2_provider/apps.py
new file mode 100644
index 0000000..6f67f38
--- /dev/null
+++ b/oauth2_provider/apps.py
@@ -0,0 +1,6 @@
+from django.apps import AppConfig
+
+
+class DOTConfig(AppConfig):
+ name = 'oauth2_provider'
+ verbose_name = "Django OAuth Toolkit"
diff --git a/oauth2_provider/compat.py b/oauth2_provider/compat.py
index 4266c34..3fca936 100644
--- a/oauth2_provider/compat.py
+++ b/oauth2_provider/compat.py
@@ -37,3 +37,9 @@ try:
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
new file mode 100644
index 0000000..21859e8
--- /dev/null
+++ b/oauth2_provider/compat_handlers.py
@@ -0,0 +1,5 @@
+# Django 1.9 drops the NullHandler since Python 2.7 includes it
+try:
+ from logging import NullHandler
+except ImportError:
+ from django.utils.log import NullHandler
diff --git a/oauth2_provider/ext/rest_framework/__init__.py b/oauth2_provider/ext/rest_framework/__init__.py
index 24cbda1..00da0a1 100644
--- a/oauth2_provider/ext/rest_framework/__init__.py
+++ b/oauth2_provider/ext/rest_framework/__init__.py
@@ -1,2 +1,2 @@
from .authentication import OAuth2Authentication
-from .permissions import TokenHasScope, TokenHasReadWriteScope
+from .permissions import TokenHasScope, TokenHasReadWriteScope, TokenHasResourceScope
diff --git a/oauth2_provider/ext/rest_framework/permissions.py b/oauth2_provider/ext/rest_framework/permissions.py
index e60415d..559bbbc 100644
--- a/oauth2_provider/ext/rest_framework/permissions.py
+++ b/oauth2_provider/ext/rest_framework/permissions.py
@@ -59,3 +59,28 @@ class TokenHasReadWriteScope(TokenHasScope):
read_write_scope = oauth2_settings.WRITE_SCOPE
return required_scopes + [read_write_scope]
+
+
+class TokenHasResourceScope(TokenHasScope):
+ """
+ The request is authenticated as a user and the token used has the right scope
+ """
+
+ def get_scopes(self, request, view):
+ try:
+ view_scopes = (
+ super(TokenHasResourceScope, self).get_scopes(request, view)
+ )
+ except ImproperlyConfigured:
+ view_scopes = []
+
+ if request.method.upper() in SAFE_HTTP_METHODS:
+ scope_type = oauth2_settings.READ_SCOPE
+ else:
+ scope_type = oauth2_settings.WRITE_SCOPE
+
+ required_scopes = [
+ '{0}:{1}'.format(scope, scope_type) for scope in view_scopes
+ ]
+
+ return required_scopes
diff --git a/oauth2_provider/forms.py b/oauth2_provider/forms.py
index a8b3985..a2b4d8f 100644
--- a/oauth2_provider/forms.py
+++ b/oauth2_provider/forms.py
@@ -1,12 +1,10 @@
from django import forms
-from .models import Application
-
class AllowForm(forms.Form):
allow = forms.BooleanField(required=False)
redirect_uri = forms.CharField(widget=forms.HiddenInput())
- scope = forms.CharField(required=False, widget=forms.HiddenInput())
+ scope = forms.CharField(widget=forms.HiddenInput())
client_id = forms.CharField(widget=forms.HiddenInput())
state = forms.CharField(required=False, widget=forms.HiddenInput())
response_type = forms.CharField(widget=forms.HiddenInput())
@@ -17,12 +15,3 @@ class AllowForm(forms.Form):
if data and 'scopes' in data:
data['scope'] = data['scopes']
return super(AllowForm, self).__init__(*args, **kwargs)
-
-
-class RegistrationForm(forms.ModelForm):
- """
- TODO: add docstring
- """
- class Meta:
- model = Application
- fields = ('name', 'client_id', 'client_secret', 'client_type', 'authorization_grant_type', 'redirect_uris')
diff --git a/oauth2_provider/management/__init__.py b/oauth2_provider/management/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/oauth2_provider/management/commands/__init__.py b/oauth2_provider/management/commands/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/oauth2_provider/management/commands/cleartokens.py b/oauth2_provider/management/commands/cleartokens.py
new file mode 100644
index 0000000..5b56d2b
--- /dev/null
+++ b/oauth2_provider/management/commands/cleartokens.py
@@ -0,0 +1,9 @@
+from django.core.management.base import BaseCommand, CommandError
+from ...models import clear_expired
+
+
+class Command(BaseCommand):
+ help = "Can be run as a cronjob or directly to clean out expired tokens"
+
+ def handle(self, *args, **options):
+ clear_expired()
diff --git a/oauth2_provider/migrations/0001_initial.py b/oauth2_provider/migrations/0001_initial.py
index bb1b518..a1c59c7 100644
--- a/oauth2_provider/migrations/0001_initial.py
+++ b/oauth2_provider/migrations/0001_initial.py
@@ -30,6 +30,7 @@ class Migration(migrations.Migration):
],
options={
'abstract': False,
+ 'swappable': 'OAUTH2_PROVIDER_APPLICATION_MODEL',
},
),
migrations.CreateModel(
diff --git a/oauth2_provider/models.py b/oauth2_provider/models.py
index 1d26726..fd3cdf4 100644
--- a/oauth2_provider/models.py
+++ b/oauth2_provider/models.py
@@ -1,7 +1,9 @@
from __future__ import unicode_literals
+from datetime import timedelta
+
from django.core.urlresolvers import reverse
-from django.db import models
+from django.db import models, transaction
from django.utils import timezone
from django.utils.translation import ugettext_lazy as _
@@ -155,6 +157,9 @@ class Grant(models.Model):
"""
Check token expiration with timezone awareness
"""
+ if not self.expires:
+ return True
+
return timezone.now() >= self.expires
def redirect_uri_allowed(self, uri):
@@ -196,6 +201,9 @@ class AccessToken(models.Model):
"""
Check token expiration with timezone awareness
"""
+ if not self.expires:
+ return True
+
return timezone.now() >= self.expires
def allow_scopes(self, scopes):
@@ -219,6 +227,13 @@ class AccessToken(models.Model):
"""
self.delete()
+ @property
+ def scopes(self):
+ """
+ Returns a dictionary of allowed scope names (as keys) with their descriptions (as values)
+ """
+ return {name: desc for name, desc in oauth2_settings.SCOPES.items() if name in self.scope.split()}
+
def __str__(self):
return self.token
@@ -266,3 +281,24 @@ def get_application_model():
e = "APPLICATION_MODEL refers to model {0} that has not been installed"
raise ImproperlyConfigured(e.format(oauth2_settings.APPLICATION_MODEL))
return app_model
+
+
+def clear_expired():
+ now = timezone.now()
+ refresh_expire_at = None
+
+ REFRESH_TOKEN_EXPIRE_SECONDS = oauth2_settings.REFRESH_TOKEN_EXPIRE_SECONDS
+ if REFRESH_TOKEN_EXPIRE_SECONDS:
+ if not isinstance(REFRESH_TOKEN_EXPIRE_SECONDS, timedelta):
+ try:
+ REFRESH_TOKEN_EXPIRE_SECONDS = timedelta(seconds=REFRESH_TOKEN_EXPIRE_SECONDS)
+ except TypeError:
+ e = "REFRESH_TOKEN_EXPIRE_SECONDS must be either a timedelta or seconds"
+ raise ImproperlyConfigured(e)
+ refresh_expire_at = now - REFRESH_TOKEN_EXPIRE_SECONDS
+
+ with transaction.atomic():
+ if refresh_expire_at:
+ RefreshToken.objects.filter(access_token__expires__lt=refresh_expire_at).delete()
+ AccessToken.objects.filter(refresh_token__isnull=True, expires__lt=now).delete()
+ Grant.objects.filter(expires__lt=now).delete()
diff --git a/oauth2_provider/oauth2_backends.py b/oauth2_provider/oauth2_backends.py
index 4c7e40a..6fbdc82 100644
--- a/oauth2_provider/oauth2_backends.py
+++ b/oauth2_provider/oauth2_backends.py
@@ -18,7 +18,7 @@ class OAuthLibCore(object):
"""
:params server: An instance of oauthlib.oauth2.Server class
"""
- self.server = server or oauth2.Server(oauth2_settings.OAUTH2_VALIDATOR_CLASS())
+ self.server = server or oauth2_settings.OAUTH2_SERVER_CLASS(oauth2_settings.OAUTH2_VALIDATOR_CLASS())
def _get_escaped_full_path(self, request):
"""
@@ -32,6 +32,17 @@ class OAuthLibCore(object):
return urlunparse(parsed)
+ def _get_extra_credentials(self, request):
+ """
+ Produce extra credentials for token response. This dictionary will be
+ merged with the response.
+ See also: `oauthlib.oauth2.rfc6749.TokenEndpoint.create_token_response`
+
+ :param request: The current django.http.HttpRequest object
+ :return: dictionary of extra credentials or None (default)
+ """
+ return None
+
def _extract_params(self, request):
"""
Extract parameters from the Django request object. Such parameters will then be passed to
@@ -121,9 +132,10 @@ class OAuthLibCore(object):
:param request: The current django.http.HttpRequest object
"""
uri, http_method, body, headers = self._extract_params(request)
+ extra_credentials = self._get_extra_credentials(request)
headers, body, status = self.server.create_token_response(uri, http_method, body,
- headers)
+ headers, extra_credentials)
uri = headers.get("Location", None)
return uri, headers, body, status
@@ -179,7 +191,6 @@ def get_oauthlib_core():
Utility function that take a request and returns an instance of
`oauth2_provider.backends.OAuthLibCore`
"""
- from oauthlib.oauth2 import Server
-
- server = Server(oauth2_settings.OAUTH2_VALIDATOR_CLASS())
+ validator = oauth2_settings.OAUTH2_VALIDATOR_CLASS()
+ server = oauth2_settings.OAUTH2_SERVER_CLASS(validator)
return oauth2_settings.OAUTH2_BACKEND_CLASS(server)
diff --git a/oauth2_provider/oauth2_validators.py b/oauth2_provider/oauth2_validators.py
index cc669f0..25908d9 100644
--- a/oauth2_provider/oauth2_validators.py
+++ b/oauth2_provider/oauth2_validators.py
@@ -1,5 +1,6 @@
from __future__ import unicode_literals
+import six
import base64
import binascii
import logging
@@ -60,6 +61,11 @@ class OAuth2Validator(RequestValidator):
except AttributeError:
encoding = 'utf-8'
+ # Encode auth_string to bytes. This is needed for python3.2 compatibility
+ # because b64decode function only supports bytes type in input.
+ if isinstance(auth_string, six.string_types):
+ auth_string = auth_string.encode(encoding)
+
try:
b64_decoded = base64.b64decode(auth_string)
except (TypeError, binascii.Error):
@@ -272,7 +278,7 @@ class OAuth2Validator(RequestValidator):
return set(scopes).issubset(set(oauth2_settings._SCOPES))
def get_default_scopes(self, client_id, request, *args, **kwargs):
- return oauth2_settings._SCOPES
+ return oauth2_settings._DEFAULT_SCOPES
def validate_redirect_uri(self, client_id, redirect_uri, request, *args, **kwargs):
return request.client.redirect_uri_allowed(redirect_uri)
diff --git a/oauth2_provider/settings.py b/oauth2_provider/settings.py
index db57686..7fb78e2 100644
--- a/oauth2_provider/settings.py
+++ b/oauth2_provider/settings.py
@@ -20,38 +20,43 @@ from __future__ import unicode_literals
import six
from django.conf import settings
+from django.core.exceptions import ImproperlyConfigured
try:
# Available in Python 2.7+
import importlib
except ImportError:
from django.utils import importlib
-
USER_SETTINGS = getattr(settings, 'OAUTH2_PROVIDER', None)
DEFAULTS = {
'CLIENT_ID_GENERATOR_CLASS': 'oauth2_provider.generators.ClientIdGenerator',
'CLIENT_SECRET_GENERATOR_CLASS': 'oauth2_provider.generators.ClientSecretGenerator',
'CLIENT_SECRET_GENERATOR_LENGTH': 128,
+ 'OAUTH2_SERVER_CLASS': 'oauthlib.oauth2.Server',
'OAUTH2_VALIDATOR_CLASS': 'oauth2_provider.oauth2_validators.OAuth2Validator',
'OAUTH2_BACKEND_CLASS': 'oauth2_provider.oauth2_backends.OAuthLibCore',
'SCOPES': {"read": "Reading scope", "write": "Writing scope"},
+ 'DEFAULT_SCOPES': ['__all__'],
'READ_SCOPE': 'read',
'WRITE_SCOPE': 'write',
'AUTHORIZATION_CODE_EXPIRE_SECONDS': 60,
'ACCESS_TOKEN_EXPIRE_SECONDS': 36000,
+ 'REFRESH_TOKEN_EXPIRE_SECONDS': None,
'APPLICATION_MODEL': getattr(settings, 'OAUTH2_PROVIDER_APPLICATION_MODEL', 'oauth2_provider.Application'),
'REQUEST_APPROVAL_PROMPT': 'force',
'ALLOWED_REDIRECT_URI_SCHEMES': ['http', 'https'],
# Special settings that will be evaluated at runtime
'_SCOPES': [],
+ '_DEFAULT_SCOPES': [],
}
# List of settings that cannot be empty
MANDATORY = (
'CLIENT_ID_GENERATOR_CLASS',
'CLIENT_SECRET_GENERATOR_CLASS',
+ 'OAUTH2_SERVER_CLASS',
'OAUTH2_VALIDATOR_CLASS',
'OAUTH2_BACKEND_CLASS',
'SCOPES',
@@ -62,6 +67,7 @@ MANDATORY = (
IMPORT_STRINGS = (
'CLIENT_ID_GENERATOR_CLASS',
'CLIENT_SECRET_GENERATOR_CLASS',
+ 'OAUTH2_SERVER_CLASS',
'OAUTH2_VALIDATOR_CLASS',
'OAUTH2_BACKEND_CLASS',
)
@@ -125,6 +131,18 @@ class OAuth2ProviderSettings(object):
# Overriding special settings
if attr == '_SCOPES':
val = list(six.iterkeys(self.SCOPES))
+ if attr == '_DEFAULT_SCOPES':
+ if '__all__' in self.DEFAULT_SCOPES:
+ # If DEFAULT_SCOPES is set to ['__all__'] the whole set of scopes is returned
+ val = list(self._SCOPES)
+ else:
+ # Otherwise we return a subset (that can be void) of SCOPES
+ val = []
+ for scope in self.DEFAULT_SCOPES:
+ if scope in self._SCOPES:
+ val.append(scope)
+ else:
+ raise ImproperlyConfigured("Defined DEFAULT_SCOPES not present in SCOPES")
self.validate_setting(attr, val)
diff --git a/oauth2_provider/templates/oauth2_provider/application_confirm_delete.html b/oauth2_provider/templates/oauth2_provider/application_confirm_delete.html
index 1651f51..b1d944f 100644
--- a/oauth2_provider/templates/oauth2_provider/application_confirm_delete.html
+++ b/oauth2_provider/templates/oauth2_provider/application_confirm_delete.html
@@ -1,7 +1,7 @@
{% extends "oauth2_provider/base.html" %}
{% load i18n %}
-{% load url from future %}
+{% load url from compat %}
{% block content %}
<div class="block-center">
<h3 class="block-center-heading">{% trans "Are you sure to delete the application" %} {{ application.name }}?</h3>
diff --git a/oauth2_provider/templates/oauth2_provider/application_detail.html b/oauth2_provider/templates/oauth2_provider/application_detail.html
index 36eb583..833f9a5 100644
--- a/oauth2_provider/templates/oauth2_provider/application_detail.html
+++ b/oauth2_provider/templates/oauth2_provider/application_detail.html
@@ -1,7 +1,7 @@
{% extends "oauth2_provider/base.html" %}
{% load i18n %}
-{% load url from future %}
+{% load url from compat %}
{% block content %}
<div class="block-center">
<h3 class="block-center-heading">{{ application.name }}</h3>
diff --git a/oauth2_provider/templates/oauth2_provider/application_form.html b/oauth2_provider/templates/oauth2_provider/application_form.html
index baa81a1..5c08ff0 100644
--- a/oauth2_provider/templates/oauth2_provider/application_form.html
+++ b/oauth2_provider/templates/oauth2_provider/application_form.html
@@ -1,7 +1,7 @@
{% extends "oauth2_provider/base.html" %}
{% load i18n %}
-{% load url from future %}
+{% load url from compat %}
{% block content %}
<div class="block-center">
<form class="form-horizontal" method="post" action="{% block app-form-action-url %}{% url 'oauth2_provider:update' application.id %}{% endblock app-form-action-url %}">
diff --git a/oauth2_provider/templates/oauth2_provider/application_list.html b/oauth2_provider/templates/oauth2_provider/application_list.html
index c500493..cb7c7c4 100644
--- a/oauth2_provider/templates/oauth2_provider/application_list.html
+++ b/oauth2_provider/templates/oauth2_provider/application_list.html
@@ -1,7 +1,7 @@
{% extends "oauth2_provider/base.html" %}
{% load i18n %}
-{% load url from future %}
+{% load url from compat %}
{% block content %}
<div class="block-center">
<h3 class="block-center-heading">{% trans "Your applications" %}</h3>
diff --git a/oauth2_provider/templates/oauth2_provider/application_registration_form.html b/oauth2_provider/templates/oauth2_provider/application_registration_form.html
index 077e231..69bebb2 100644
--- a/oauth2_provider/templates/oauth2_provider/application_registration_form.html
+++ b/oauth2_provider/templates/oauth2_provider/application_registration_form.html
@@ -1,7 +1,7 @@
{% extends "oauth2_provider/application_form.html" %}
{% load i18n %}
-{% load url from future %}
+{% load url from compat %}
{% block app-form-title %}{% trans "Register a new application" %}{% endblock app-form-title %}
diff --git a/oauth2_provider/templates/oauth2_provider/authorized-token-delete.html b/oauth2_provider/templates/oauth2_provider/authorized-token-delete.html
new file mode 100644
index 0000000..e08233a
--- /dev/null
+++ b/oauth2_provider/templates/oauth2_provider/authorized-token-delete.html
@@ -0,0 +1,9 @@
+{% extends "oauth2_provider/base.html" %}
+
+{% load i18n %}
+{% block content %}
+ <form action="" method="post">{% csrf_token %}
+ <p>{% trans "Are you sure you want to delete this token?" %}</p>
+ <input type="submit" value="{% trans "Delete" %}" />
+ </form>
+{% endblock %}
diff --git a/oauth2_provider/templates/oauth2_provider/authorized-tokens.html b/oauth2_provider/templates/oauth2_provider/authorized-tokens.html
new file mode 100644
index 0000000..f25069e
--- /dev/null
+++ b/oauth2_provider/templates/oauth2_provider/authorized-tokens.html
@@ -0,0 +1,24 @@
+{% extends "oauth2_provider/base.html" %}
+
+{% load i18n %}
+{% load url from compat %}
+{% block content %}
+ <div class="block-center">
+ <h1>{% trans "Tokens" %}</h1>
+ <ul>
+ {% for authorized_token in authorized_tokens %}
+ <li>
+ {{ authorized_token.application }}
+ (<a href="{% url 'oauth2_provider:authorized-token-delete' authorized_token.pk %}">revoke</a>)
+ </li>
+ <ul>
+ {% for scope_name, scope_description in authorized_token.scopes.items %}
+ <li>{{ scope_name }}: {{ scope_description }}</li>
+ {% endfor %}
+ </ul>
+ {% empty %}
+ <li>{% trans "There are no authorized tokens yet." %}</li>
+ {% endfor %}
+ </ul>
+ </div>
+{% endblock %}
diff --git a/oauth2_provider/templatetags/__init__.py b/oauth2_provider/templatetags/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/oauth2_provider/templatetags/compat.py b/oauth2_provider/templatetags/compat.py
new file mode 100644
index 0000000..8fbc8b0
--- /dev/null
+++ b/oauth2_provider/templatetags/compat.py
@@ -0,0 +1,10 @@
+from django import template
+
+from ..compat import url as url_compat
+
+register = template.Library()
+
+
+ at register.tag
+def url(parser, token):
+ return url_compat(parser, token)
diff --git a/oauth2_provider/tests/settings.py b/oauth2_provider/tests/settings.py
index 47ffae0..e144e80 100644
--- a/oauth2_provider/tests/settings.py
... 1037 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