Skip to content
This repository was archived by the owner on Mar 3, 2020. It is now read-only.

Commit 05673d9

Browse files
brandon-milesclintonb
authored andcommitted
Added support for client_credentials grant type
1 parent 1c60a14 commit 05673d9

File tree

5 files changed

+63
-7
lines changed

5 files changed

+63
-7
lines changed

provider/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
__version__ = '0.4.0'
1+
__version__ = '0.5.0'

provider/oauth2/forms.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -336,3 +336,10 @@ def clean(self):
336336

337337
data['client'] = client
338338
return data
339+
340+
341+
class ClientCredentialsGrantForm(ScopeMixin, OAuthForm):
342+
"""
343+
Validate a client credentials grant request.
344+
"""
345+
scope = ScopeChoiceField(choices=SCOPE_NAMES, required=False)

provider/oauth2/tests.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -597,6 +597,25 @@ def test_access_token_response_valid_token_type(self):
597597
token = self._login_authorize_get_token()
598598
self.assertEqual(token['token_type'], constants.TOKEN_TYPE, token)
599599

600+
def test_client_credentials_grant(self):
601+
response = self.client.post(self.access_token_url(), {
602+
'grant_type': 'client_credentials',
603+
'client_id': self.get_client().client_id,
604+
'client_secret': self.get_client().client_secret,
605+
})
606+
607+
self.assertEqual(200, response.status_code, response.content)
608+
609+
response = self.client.post(self.access_token_url(), {
610+
'grant_type': 'client_credentials',
611+
'client_id': self.get_client().client_id,
612+
'client_secret': self.get_client().client_secret + 'invalid',
613+
})
614+
615+
self.assertEqual(400, response.status_code, response.content)
616+
self.assertEqual('invalid_client',
617+
json.loads(response.content)['error'])
618+
600619

601620
class AuthBackendTest(BaseOAuth2TestCase):
602621
fixtures = ['test_oauth2']

provider/oauth2/views.py

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,11 @@
88

99
from provider import constants
1010
from provider.oauth2.backends import BasicClientBackend, RequestParamsClientBackend, PublicPasswordBackend
11-
from provider.oauth2.forms import AuthorizationCodeGrantForm
12-
from provider.oauth2.forms import AuthorizationRequestForm, AuthorizationForm
13-
from provider.oauth2.forms import PasswordGrantForm, RefreshTokenGrantForm
11+
from provider.oauth2.forms import (AuthorizationCodeGrantForm, AuthorizationRequestForm, AuthorizationForm,
12+
PasswordGrantForm, RefreshTokenGrantForm, ClientCredentialsGrantForm)
1413
from provider.oauth2.models import Client, RefreshToken, AccessToken
1514
from provider.utils import now
16-
from provider.views import AccessToken as AccessTokenView, OAuthError, AccessTokenMixin
17-
from provider.views import Capture, Authorize, Redirect
15+
from provider.views import AccessToken as AccessTokenView, OAuthError, AccessTokenMixin, Capture, Authorize, Redirect
1816

1917

2018
class OAuth2AccessTokenMixin(AccessTokenMixin):
@@ -143,6 +141,12 @@ def get_password_grant(self, request, data, client):
143141
raise OAuthError(form.errors)
144142
return form.cleaned_data
145143

144+
def get_client_credentials_grant(self, request, data, client):
145+
form = ClientCredentialsGrantForm(data, client=client)
146+
if not form.is_valid():
147+
raise OAuthError(form.errors)
148+
return form.cleaned_data
149+
146150
def invalidate_grant(self, grant):
147151
if constants.DELETE_EXPIRED:
148152
grant.delete()

provider/views.py

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -503,7 +503,7 @@ class AccessToken(OAuthView, Mixin, AccessTokenMixin):
503503
Authentication backends used to authenticate a particular client.
504504
"""
505505

506-
grant_types = ['authorization_code', 'refresh_token', 'password']
506+
grant_types = ['authorization_code', 'refresh_token', 'password', 'client_credentials']
507507
"""
508508
The default grant types supported by this view.
509509
"""
@@ -532,6 +532,14 @@ def get_password_grant(self, request, data, client):
532532
"""
533533
raise NotImplementedError # pragma: no cover
534534

535+
def get_client_credentials_grant(self, request, data, client):
536+
"""
537+
Return the optional parameters (scope) associated with this request.
538+
539+
:return: ``tuple`` - ``(True or False, options)``
540+
"""
541+
raise NotImplementedError
542+
535543
def invalidate_grant(self, grant):
536544
"""
537545
Override to handle grant invalidation. A grant is invalidated right
@@ -610,6 +618,22 @@ def password(self, request, data, client):
610618

611619
return self.access_token_response(at)
612620

621+
def client_credentials(self, request, data, client):
622+
"""
623+
Handle ``grant_type=client_credentials`` requests as defined in
624+
:rfc:`4.4`.
625+
"""
626+
data = self.get_client_credentials_grant(request, data, client)
627+
scope = data.get('scope')
628+
629+
if constants.SINGLE_ACCESS_TOKEN:
630+
at = self.get_access_token(request, client.user, scope, client)
631+
else:
632+
at = self.create_access_token(request, client.user, scope, client)
633+
rt = self.create_refresh_token(request, client.user, scope, at, client)
634+
635+
return self.access_token_response(at)
636+
613637
def get_handler(self, grant_type):
614638
"""
615639
Return a function or method that is capable handling the ``grant_type``
@@ -622,6 +646,8 @@ def get_handler(self, grant_type):
622646
return self.refresh_token
623647
elif grant_type == 'password':
624648
return self.password
649+
elif grant_type == 'client_credentials':
650+
return self.client_credentials
625651
return None
626652

627653
def get(self, request):

0 commit comments

Comments
 (0)