Skip to content

Commit f8ce862

Browse files
committed
Merge branch 'oauthlib_backend_class' into master
2 parents df9e752 + cfff85a commit f8ce862

File tree

11 files changed

+144
-18
lines changed

11 files changed

+144
-18
lines changed

README.rst

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,12 @@ Roadmap / Todo list (help wanted)
9090
Changelog
9191
---------
9292

93+
master branch
94+
~~~~~~~~~~~~~
95+
96+
* ``oauthlib_backend_class`` is now pluggable through Django settings
97+
* #127: ``application/json`` Content-Type is now supported using ``JSONOAuthLibCore``
98+
9399
0.8.0 [2015-03-27]
94100
~~~~~~~~~~~~~~~~~~
95101

docs/changelog.rst

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
11
Changelog
22
=========
33

4+
master branch
5+
-------------
6+
7+
* ``oauthlib_backend_class`` is now pluggable through Django settings
8+
* #127: ``application/json`` Content-Type is now supported using ``JSONOAuthLibCore``
9+
410
0.8.0 [2015-03-27]
511
------------------
612

docs/settings.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,11 @@ OAUTH2_VALIDATOR_CLASS
6464
The import string of the ``oauthlib.oauth2.RequestValidator`` subclass that
6565
validates every step of the OAuth2 process.
6666

67+
OAUTH2_BACKEND_CLASS
68+
~~~~~~~~~~~~~~~~~~~~
69+
The import string for the ``oauthlib_backend_class`` used in the ``OAuthLibMixin``,
70+
to get a ``Server`` instance.
71+
6772
SCOPES
6873
~~~~~~
6974
A dictionnary mapping each scope name to its human description.

oauth2_provider/oauth2_backends.py

Lines changed: 42 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
from __future__ import unicode_literals
22

3+
import json
4+
35
from oauthlib import oauth2
46
from oauthlib.common import urlencode, urlencoded, quote
57

@@ -33,19 +35,37 @@ def _get_escaped_full_path(self, request):
3335
def _extract_params(self, request):
3436
"""
3537
Extract parameters from the Django request object. Such parameters will then be passed to
36-
OAuthLib to build its own Request object
38+
OAuthLib to build its own Request object. The body should be encoded using OAuthLib urlencoded
3739
"""
3840
uri = self._get_escaped_full_path(request)
3941
http_method = request.method
42+
headers = self.extract_headers(request)
43+
body = urlencode(self.extract_body(request))
44+
return uri, http_method, body, headers
45+
46+
def extract_headers(self, request):
47+
"""
48+
Extracts headers from the Django request object
49+
:param request: The current django.http.HttpRequest object
50+
:return: a dictionary with OAuthLib needed headers
51+
"""
4052
headers = request.META.copy()
4153
if 'wsgi.input' in headers:
4254
del headers['wsgi.input']
4355
if 'wsgi.errors' in headers:
4456
del headers['wsgi.errors']
4557
if 'HTTP_AUTHORIZATION' in headers:
4658
headers['Authorization'] = headers['HTTP_AUTHORIZATION']
47-
body = urlencode(request.POST.items())
48-
return uri, http_method, body, headers
59+
60+
return headers
61+
62+
def extract_body(self, request):
63+
"""
64+
Extracts the POST body from the Django request object
65+
:param request: The current django.http.HttpRequest object
66+
:return: provided POST parameters
67+
"""
68+
return request.POST.items()
4969

5070
def validate_authorization_request(self, request):
5171
"""
@@ -136,6 +156,24 @@ def verify_request(self, request, scopes):
136156
return valid, r
137157

138158

159+
class JSONOAuthLibCore(OAuthLibCore):
160+
"""
161+
Extends the default OAuthLibCore to parse correctly requests with application/json Content-Type
162+
"""
163+
def extract_body(self, request):
164+
"""
165+
Extracts the JSON body from the Django request object
166+
:param request: The current django.http.HttpRequest object
167+
:return: provided POST parameters "urlencodable"
168+
"""
169+
try:
170+
body = json.loads(request.body.decode('utf-8')).items()
171+
except ValueError:
172+
body = ""
173+
174+
return body
175+
176+
139177
def get_oauthlib_core():
140178
"""
141179
Utility function that take a request and returns an instance of
@@ -144,4 +182,4 @@ def get_oauthlib_core():
144182
from oauthlib.oauth2 import Server
145183

146184
server = Server(oauth2_settings.OAUTH2_VALIDATOR_CLASS())
147-
return OAuthLibCore(server)
185+
return oauth2_settings.OAUTH2_BACKEND_CLASS(server)

oauth2_provider/settings.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
'CLIENT_SECRET_GENERATOR_CLASS': 'oauth2_provider.generators.ClientSecretGenerator',
3535
'CLIENT_SECRET_GENERATOR_LENGTH': 128,
3636
'OAUTH2_VALIDATOR_CLASS': 'oauth2_provider.oauth2_validators.OAuth2Validator',
37+
'OAUTH2_BACKEND_CLASS': 'oauth2_provider.oauth2_backends.OAuthLibCore',
3738
'SCOPES': {"read": "Reading scope", "write": "Writing scope"},
3839
'READ_SCOPE': 'read',
3940
'WRITE_SCOPE': 'write',
@@ -52,6 +53,7 @@
5253
'CLIENT_ID_GENERATOR_CLASS',
5354
'CLIENT_SECRET_GENERATOR_CLASS',
5455
'OAUTH2_VALIDATOR_CLASS',
56+
'OAUTH2_BACKEND_CLASS',
5557
'SCOPES',
5658
'ALLOWED_REDIRECT_URI_SCHEMES',
5759
)
@@ -61,6 +63,7 @@
6163
'CLIENT_ID_GENERATOR_CLASS',
6264
'CLIENT_SECRET_GENERATOR_CLASS',
6365
'OAUTH2_VALIDATOR_CLASS',
66+
'OAUTH2_BACKEND_CLASS',
6467
)
6568

6669

oauth2_provider/tests/test_client_credential.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
from oauthlib.oauth2 import BackendApplicationServer
1515

1616
from ..models import get_application_model, AccessToken
17+
from ..oauth2_backends import OAuthLibCore
1718
from ..oauth2_validators import OAuth2Validator
1819
from ..settings import oauth2_settings
1920
from ..views import ProtectedResourceView
@@ -114,6 +115,7 @@ def test_extended_request(self):
114115
class TestView(OAuthLibMixin, View):
115116
server_class = BackendApplicationServer
116117
validator_class = OAuth2Validator
118+
oauthlib_backend_class = OAuthLibCore
117119

118120
def get_scopes(self):
119121
return ['read', 'write']

oauth2_provider/tests/test_mixins.py

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
from oauthlib.oauth2 import Server
99

1010
from ..views.mixins import OAuthLibMixin, ScopedResourceMixin, ProtectedResourceMixin
11+
from ..oauth2_backends import OAuthLibCore
1112
from ..oauth2_validators import OAuth2Validator
1213

1314

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

1920

2021
class TestOAuthLibMixin(BaseTest):
22+
def test_missing_oauthlib_backend_class(self):
23+
class TestView(OAuthLibMixin, View):
24+
server_class = Server
25+
validator_class = OAuth2Validator
26+
27+
test_view = TestView()
28+
29+
self.assertRaises(ImproperlyConfigured, test_view.get_oauthlib_backend_class)
30+
2131
def test_missing_server_class(self):
2232
class TestView(OAuthLibMixin, View):
2333
validator_class = OAuth2Validator
34+
oauthlib_backend_class = OAuthLibCore
2435

2536
test_view = TestView()
2637

@@ -29,6 +40,7 @@ class TestView(OAuthLibMixin, View):
2940
def test_missing_validator_class(self):
3041
class TestView(OAuthLibMixin, View):
3142
server_class = Server
43+
oauthlib_backend_class = OAuthLibCore
3244

3345
test_view = TestView()
3446

@@ -38,6 +50,7 @@ def test_correct_server(self):
3850
class TestView(OAuthLibMixin, View):
3951
server_class = Server
4052
validator_class = OAuth2Validator
53+
oauthlib_backend_class = OAuthLibCore
4154

4255
request = self.request_factory.get("/fake-req")
4356
request.user = "fake"
@@ -52,13 +65,13 @@ class AnotherOauthLibBackend(object):
5265
class TestView(OAuthLibMixin, View):
5366
server_class = Server
5467
validator_class = OAuth2Validator
55-
oauthlib_core_class = AnotherOauthLibBackend
68+
oauthlib_backend_class = AnotherOauthLibBackend
5669

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

61-
self.assertEqual(test_view.get_oauthlib_core_class(),
74+
self.assertEqual(test_view.get_oauthlib_backend_class(),
6275
AnotherOauthLibBackend)
6376

6477

oauth2_provider/tests/test_oauth2_backends.py

Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,56 @@
1-
from django.test import TestCase, RequestFactory
1+
import json
22

3+
from django.test import TestCase, RequestFactory
34

45
from ..backends import get_oauthlib_core
6+
from ..oauth2_backends import OAuthLibCore, JSONOAuthLibCore
7+
8+
9+
class TestOAuthLibCoreBackend(TestCase):
10+
def setUp(self):
11+
self.factory = RequestFactory()
12+
self.oauthlib_core = OAuthLibCore()
13+
14+
def test_form_urlencoded_extract_params(self):
15+
payload = "grant_type=password&username=john&password=123456"
16+
request = self.factory.post("/o/token/", payload, content_type="application/x-www-form-urlencoded")
17+
18+
uri, http_method, body, headers = self.oauthlib_core._extract_params(request)
19+
self.assertIn("grant_type=password", body)
20+
self.assertIn("username=john", body)
21+
self.assertIn("password=123456", body)
22+
23+
def test_application_json_extract_params(self):
24+
payload = json.dumps({
25+
"grant_type": "password",
26+
"username": "john",
27+
"password": "123456",
28+
})
29+
request = self.factory.post("/o/token/", payload, content_type="application/json")
30+
31+
uri, http_method, body, headers = self.oauthlib_core._extract_params(request)
32+
self.assertNotIn("grant_type=password", body)
33+
self.assertNotIn("username=john", body)
34+
self.assertNotIn("password=123456", body)
35+
36+
37+
class TestJSONOAuthLibCoreBackend(TestCase):
38+
def setUp(self):
39+
self.factory = RequestFactory()
40+
self.oauthlib_core = JSONOAuthLibCore()
41+
42+
def test_application_json_extract_params(self):
43+
payload = json.dumps({
44+
"grant_type": "password",
45+
"username": "john",
46+
"password": "123456",
47+
})
48+
request = self.factory.post("/o/token/", payload, content_type="application/json")
49+
50+
uri, http_method, body, headers = self.oauthlib_core._extract_params(request)
51+
self.assertIn("grant_type=password", body)
52+
self.assertIn("username=john", body)
53+
self.assertIn("password=123456", body)
554

655

756
class TestOAuthLibCore(TestCase):

oauth2_provider/views/base.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ class AuthorizationView(BaseAuthorizationView, FormView):
7373

7474
server_class = Server
7575
validator_class = oauth2_settings.OAUTH2_VALIDATOR_CLASS
76+
oauthlib_backend_class = oauth2_settings.OAUTH2_BACKEND_CLASS
7677

7778
skip_authorization_completely = False
7879

@@ -164,6 +165,7 @@ class TokenView(CsrfExemptMixin, OAuthLibMixin, View):
164165
"""
165166
server_class = Server
166167
validator_class = oauth2_settings.OAUTH2_VALIDATOR_CLASS
168+
oauthlib_backend_class = oauth2_settings.OAUTH2_BACKEND_CLASS
167169

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

185188
def post(self, request, *args, **kwargs):
186189
url, headers, body, status = self.create_revocation_response(request)

oauth2_provider/views/generic.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ class ProtectedResourceView(ProtectedResourceMixin, View):
1212
"""
1313
server_class = Server
1414
validator_class = oauth2_settings.OAUTH2_VALIDATOR_CLASS
15+
oauthlib_backend_class = oauth2_settings.OAUTH2_BACKEND_CLASS
1516

1617

1718
class ScopedProtectedResourceView(ScopedResourceMixin, ProtectedResourceView):

0 commit comments

Comments
 (0)