Skip to content

Commit

Permalink
Merge branch 'oauthlib_backend_class' into master
Browse files Browse the repository at this point in the history
  • Loading branch information
palazzem committed Apr 20, 2015
2 parents df9e752 + cfff85a commit f8ce862
Show file tree
Hide file tree
Showing 11 changed files with 144 additions and 18 deletions.
6 changes: 6 additions & 0 deletions README.rst
Expand Up @@ -90,6 +90,12 @@ Roadmap / Todo list (help wanted)
Changelog
---------

master branch
~~~~~~~~~~~~~

* ``oauthlib_backend_class`` is now pluggable through Django settings
* #127: ``application/json`` Content-Type is now supported using ``JSONOAuthLibCore``

0.8.0 [2015-03-27]
~~~~~~~~~~~~~~~~~~

Expand Down
6 changes: 6 additions & 0 deletions docs/changelog.rst
@@ -1,6 +1,12 @@
Changelog
=========

master branch
-------------

* ``oauthlib_backend_class`` is now pluggable through Django settings
* #127: ``application/json`` Content-Type is now supported using ``JSONOAuthLibCore``

0.8.0 [2015-03-27]
------------------

Expand Down
5 changes: 5 additions & 0 deletions docs/settings.rst
Expand Up @@ -64,6 +64,11 @@ OAUTH2_VALIDATOR_CLASS
The import string of the ``oauthlib.oauth2.RequestValidator`` subclass that
validates every step of the OAuth2 process.

OAUTH2_BACKEND_CLASS
~~~~~~~~~~~~~~~~~~~~
The import string for the ``oauthlib_backend_class`` used in the ``OAuthLibMixin``,
to get a ``Server`` instance.

SCOPES
~~~~~~
A dictionnary mapping each scope name to its human description.
Expand Down
46 changes: 42 additions & 4 deletions oauth2_provider/oauth2_backends.py
@@ -1,5 +1,7 @@
from __future__ import unicode_literals

import json

from oauthlib import oauth2
from oauthlib.common import urlencode, urlencoded, quote

Expand Down Expand Up @@ -33,19 +35,37 @@ def _get_escaped_full_path(self, request):
def _extract_params(self, request):
"""
Extract parameters from the Django request object. Such parameters will then be passed to
OAuthLib to build its own Request object
OAuthLib to build its own Request object. The body should be encoded using OAuthLib urlencoded
"""
uri = self._get_escaped_full_path(request)
http_method = request.method
headers = self.extract_headers(request)
body = urlencode(self.extract_body(request))
return uri, http_method, body, headers

def extract_headers(self, request):
"""
Extracts headers from the Django request object
:param request: The current django.http.HttpRequest object
:return: a dictionary with OAuthLib needed headers
"""
headers = request.META.copy()
if 'wsgi.input' in headers:
del headers['wsgi.input']
if 'wsgi.errors' in headers:
del headers['wsgi.errors']
if 'HTTP_AUTHORIZATION' in headers:
headers['Authorization'] = headers['HTTP_AUTHORIZATION']
body = urlencode(request.POST.items())
return uri, http_method, body, headers

return headers

def extract_body(self, request):
"""
Extracts the POST body from the Django request object
:param request: The current django.http.HttpRequest object
:return: provided POST parameters
"""
return request.POST.items()

def validate_authorization_request(self, request):
"""
Expand Down Expand Up @@ -136,6 +156,24 @@ def verify_request(self, request, scopes):
return valid, r


class JSONOAuthLibCore(OAuthLibCore):
"""
Extends the default OAuthLibCore to parse correctly requests with application/json Content-Type
"""
def extract_body(self, request):
"""
Extracts the JSON body from the Django request object
:param request: The current django.http.HttpRequest object
:return: provided POST parameters "urlencodable"
"""
try:
body = json.loads(request.body.decode('utf-8')).items()
except ValueError:
body = ""

return body


def get_oauthlib_core():
"""
Utility function that take a request and returns an instance of
Expand All @@ -144,4 +182,4 @@ def get_oauthlib_core():
from oauthlib.oauth2 import Server

server = Server(oauth2_settings.OAUTH2_VALIDATOR_CLASS())
return OAuthLibCore(server)
return oauth2_settings.OAUTH2_BACKEND_CLASS(server)
3 changes: 3 additions & 0 deletions oauth2_provider/settings.py
Expand Up @@ -34,6 +34,7 @@
'CLIENT_SECRET_GENERATOR_CLASS': 'oauth2_provider.generators.ClientSecretGenerator',
'CLIENT_SECRET_GENERATOR_LENGTH': 128,
'OAUTH2_VALIDATOR_CLASS': 'oauth2_provider.oauth2_validators.OAuth2Validator',
'OAUTH2_BACKEND_CLASS': 'oauth2_provider.oauth2_backends.OAuthLibCore',
'SCOPES': {"read": "Reading scope", "write": "Writing scope"},
'READ_SCOPE': 'read',
'WRITE_SCOPE': 'write',
Expand All @@ -52,6 +53,7 @@
'CLIENT_ID_GENERATOR_CLASS',
'CLIENT_SECRET_GENERATOR_CLASS',
'OAUTH2_VALIDATOR_CLASS',
'OAUTH2_BACKEND_CLASS',
'SCOPES',
'ALLOWED_REDIRECT_URI_SCHEMES',
)
Expand All @@ -61,6 +63,7 @@
'CLIENT_ID_GENERATOR_CLASS',
'CLIENT_SECRET_GENERATOR_CLASS',
'OAUTH2_VALIDATOR_CLASS',
'OAUTH2_BACKEND_CLASS',
)


Expand Down
2 changes: 2 additions & 0 deletions oauth2_provider/tests/test_client_credential.py
Expand Up @@ -14,6 +14,7 @@
from oauthlib.oauth2 import BackendApplicationServer

from ..models import get_application_model, AccessToken
from ..oauth2_backends import OAuthLibCore
from ..oauth2_validators import OAuth2Validator
from ..settings import oauth2_settings
from ..views import ProtectedResourceView
Expand Down Expand Up @@ -114,6 +115,7 @@ def test_extended_request(self):
class TestView(OAuthLibMixin, View):
server_class = BackendApplicationServer
validator_class = OAuth2Validator
oauthlib_backend_class = OAuthLibCore

def get_scopes(self):
return ['read', 'write']
Expand Down
17 changes: 15 additions & 2 deletions oauth2_provider/tests/test_mixins.py
Expand Up @@ -8,6 +8,7 @@
from oauthlib.oauth2 import Server

from ..views.mixins import OAuthLibMixin, ScopedResourceMixin, ProtectedResourceMixin
from ..oauth2_backends import OAuthLibCore
from ..oauth2_validators import OAuth2Validator


Expand All @@ -18,9 +19,19 @@ def setUpClass(cls):


class TestOAuthLibMixin(BaseTest):
def test_missing_oauthlib_backend_class(self):
class TestView(OAuthLibMixin, View):
server_class = Server
validator_class = OAuth2Validator

test_view = TestView()

self.assertRaises(ImproperlyConfigured, test_view.get_oauthlib_backend_class)

def test_missing_server_class(self):
class TestView(OAuthLibMixin, View):
validator_class = OAuth2Validator
oauthlib_backend_class = OAuthLibCore

test_view = TestView()

Expand All @@ -29,6 +40,7 @@ class TestView(OAuthLibMixin, View):
def test_missing_validator_class(self):
class TestView(OAuthLibMixin, View):
server_class = Server
oauthlib_backend_class = OAuthLibCore

test_view = TestView()

Expand All @@ -38,6 +50,7 @@ def test_correct_server(self):
class TestView(OAuthLibMixin, View):
server_class = Server
validator_class = OAuth2Validator
oauthlib_backend_class = OAuthLibCore

request = self.request_factory.get("/fake-req")
request.user = "fake"
Expand All @@ -52,13 +65,13 @@ class AnotherOauthLibBackend(object):
class TestView(OAuthLibMixin, View):
server_class = Server
validator_class = OAuth2Validator
oauthlib_core_class = AnotherOauthLibBackend
oauthlib_backend_class = AnotherOauthLibBackend

request = self.request_factory.get("/fake-req")
request.user = "fake"
test_view = TestView()

self.assertEqual(test_view.get_oauthlib_core_class(),
self.assertEqual(test_view.get_oauthlib_backend_class(),
AnotherOauthLibBackend)


Expand Down
51 changes: 50 additions & 1 deletion oauth2_provider/tests/test_oauth2_backends.py
@@ -1,7 +1,56 @@
from django.test import TestCase, RequestFactory
import json

from django.test import TestCase, RequestFactory

from ..backends import get_oauthlib_core
from ..oauth2_backends import OAuthLibCore, JSONOAuthLibCore


class TestOAuthLibCoreBackend(TestCase):
def setUp(self):
self.factory = RequestFactory()
self.oauthlib_core = OAuthLibCore()

def test_form_urlencoded_extract_params(self):
payload = "grant_type=password&username=john&password=123456"
request = self.factory.post("/o/token/", payload, content_type="application/x-www-form-urlencoded")

uri, http_method, body, headers = self.oauthlib_core._extract_params(request)
self.assertIn("grant_type=password", body)
self.assertIn("username=john", body)
self.assertIn("password=123456", body)

def test_application_json_extract_params(self):
payload = json.dumps({
"grant_type": "password",
"username": "john",
"password": "123456",
})
request = self.factory.post("/o/token/", payload, content_type="application/json")

uri, http_method, body, headers = self.oauthlib_core._extract_params(request)
self.assertNotIn("grant_type=password", body)
self.assertNotIn("username=john", body)
self.assertNotIn("password=123456", body)


class TestJSONOAuthLibCoreBackend(TestCase):
def setUp(self):
self.factory = RequestFactory()
self.oauthlib_core = JSONOAuthLibCore()

def test_application_json_extract_params(self):
payload = json.dumps({
"grant_type": "password",
"username": "john",
"password": "123456",
})
request = self.factory.post("/o/token/", payload, content_type="application/json")

uri, http_method, body, headers = self.oauthlib_core._extract_params(request)
self.assertIn("grant_type=password", body)
self.assertIn("username=john", body)
self.assertIn("password=123456", body)


class TestOAuthLibCore(TestCase):
Expand Down
3 changes: 3 additions & 0 deletions oauth2_provider/views/base.py
Expand Up @@ -73,6 +73,7 @@ class AuthorizationView(BaseAuthorizationView, FormView):

server_class = Server
validator_class = oauth2_settings.OAUTH2_VALIDATOR_CLASS
oauthlib_backend_class = oauth2_settings.OAUTH2_BACKEND_CLASS

skip_authorization_completely = False

Expand Down Expand Up @@ -164,6 +165,7 @@ class TokenView(CsrfExemptMixin, OAuthLibMixin, View):
"""
server_class = Server
validator_class = oauth2_settings.OAUTH2_VALIDATOR_CLASS
oauthlib_backend_class = oauth2_settings.OAUTH2_BACKEND_CLASS

@method_decorator(sensitive_post_parameters('password'))
def post(self, request, *args, **kwargs):
Expand All @@ -181,6 +183,7 @@ class RevokeTokenView(CsrfExemptMixin, OAuthLibMixin, View):
"""
server_class = Server
validator_class = oauth2_settings.OAUTH2_VALIDATOR_CLASS
oauthlib_backend_class = oauth2_settings.OAUTH2_BACKEND_CLASS

def post(self, request, *args, **kwargs):
url, headers, body, status = self.create_revocation_response(request)
Expand Down
1 change: 1 addition & 0 deletions oauth2_provider/views/generic.py
Expand Up @@ -12,6 +12,7 @@ class ProtectedResourceView(ProtectedResourceMixin, View):
"""
server_class = Server
validator_class = oauth2_settings.OAUTH2_VALIDATOR_CLASS
oauthlib_backend_class = oauth2_settings.OAUTH2_BACKEND_CLASS


class ScopedProtectedResourceView(ScopedResourceMixin, ProtectedResourceView):
Expand Down
22 changes: 11 additions & 11 deletions oauth2_provider/views/mixins.py
Expand Up @@ -5,7 +5,6 @@
from django.core.exceptions import ImproperlyConfigured
from django.http import HttpResponseForbidden

from ..oauth2_backends import OAuthLibCore
from ..exceptions import FatalClientError
from ..settings import oauth2_settings

Expand All @@ -25,11 +24,12 @@ class OAuthLibMixin(object):
* server_class
* validator_class
* oauthlib_core_class # TODO rename it as oauthlib_backend_class
* oauthlib_backend_class
"""
server_class = None
validator_class = None
oauthlib_backend_class = None

@classmethod
def get_server_class(cls):
Expand All @@ -56,16 +56,16 @@ def get_validator_class(cls):
return cls.validator_class

@classmethod
def get_oauthlib_core_class(cls):
def get_oauthlib_backend_class(cls):
"""
Return the OAuthLibCore implementation class to use, silently
defaults to OAuthLibCore class from oauth2_provider package
# TODO rename this as get_oauthlib_backend_class
Return the OAuthLibCore implementation class to use
"""
if not hasattr(cls, 'oauthlib_core_class'):
return OAuthLibCore
return cls.oauthlib_core_class
if cls.oauthlib_backend_class is None:
raise ImproperlyConfigured(
"OAuthLibMixin requires either a definition of 'oauthlib_backend_class'"
" or an implementation of 'get_oauthlib_backend_class()'")
else:
return cls.oauthlib_backend_class

@classmethod
def get_server(cls):
Expand All @@ -84,7 +84,7 @@ def get_oauthlib_core(cls):
"""
if not hasattr(cls, '_oauthlib_core'):
server = cls.get_server()
core_class = cls.get_oauthlib_core_class()
core_class = cls.get_oauthlib_backend_class()
cls._oauthlib_core = core_class(server)
return cls._oauthlib_core

Expand Down

0 comments on commit f8ce862

Please sign in to comment.