Removing need to client_secret during password grants for public clients
As discussed in issues #21 and #25. This provides a more rigerous check of the circumstances required for the client_secret to forgone.
This commit is contained in:
parent
cdce08d257
commit
ad7d4f22e1
|
@ -1,6 +1,8 @@
|
|||
from django.contrib.auth import authenticate
|
||||
|
||||
from ..utils import now
|
||||
from .forms import ClientAuthForm
|
||||
from .models import AccessToken
|
||||
from .forms import ClientAuthForm, PublicPasswordGrantForm
|
||||
from .models import AccessToken, Client
|
||||
|
||||
|
||||
class BaseBackend(object):
|
||||
|
@ -61,6 +63,44 @@ class RequestParamsClientBackend(object):
|
|||
return None
|
||||
|
||||
|
||||
class RequestParamsClientBackend(object):
|
||||
"""
|
||||
Backend that tries to authenticate a client through request parameters
|
||||
which might be in the request body or URI as defined in :rfc:`2.3.1`.
|
||||
"""
|
||||
def authenticate(self, request=None):
|
||||
if request is None:
|
||||
return None
|
||||
|
||||
form = ClientAuthForm(request.REQUEST)
|
||||
|
||||
if form.is_valid():
|
||||
return form.cleaned_data.get('client')
|
||||
|
||||
return None
|
||||
|
||||
class PublicPasswordBackend(object):
|
||||
"""
|
||||
Backend that tries to authenticate a client using username, password
|
||||
and client ID. This is only available in specific circumstances:
|
||||
|
||||
- grant_type is "password"
|
||||
- client.client_type is 'public'
|
||||
"""
|
||||
|
||||
def authenticate(self, request=None):
|
||||
if request is None:
|
||||
return None
|
||||
|
||||
form = PublicPasswordGrantForm(request.REQUEST)
|
||||
|
||||
if form.is_valid():
|
||||
return form.cleaned_data.get('client')
|
||||
|
||||
return None
|
||||
|
||||
|
||||
|
||||
class AccessTokenBackend(object):
|
||||
"""
|
||||
Authenticate a user via access token and client object.
|
||||
|
|
|
@ -301,3 +301,34 @@ class PasswordGrantForm(ScopeMixin, OAuthForm):
|
|||
|
||||
data['user'] = user
|
||||
return data
|
||||
|
||||
class PublicPasswordGrantForm(PasswordGrantForm):
|
||||
client_id = forms.CharField(required=True)
|
||||
grant_type = forms.CharField(required=True)
|
||||
|
||||
def clean_grant_type(self):
|
||||
grant_type = self.cleaned_data.get('grant_type')
|
||||
|
||||
if grant_type != 'password':
|
||||
raise OAuthValidationError({'error': 'invalid_grant'})
|
||||
|
||||
return grant_type
|
||||
|
||||
def clean(self):
|
||||
data = super(PublicPasswordGrantForm, self).clean()
|
||||
|
||||
try:
|
||||
client = Client.objects.get(client_id=data.get('client_id'))
|
||||
except Client.DoesNotExist:
|
||||
raise OAuthValidationError({'error': 'invalid_client'})
|
||||
|
||||
if client.client_type != 1: # public
|
||||
raise OAuthValidationError({'error': 'invalid_client'})
|
||||
|
||||
data['client'] = client
|
||||
return data
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -334,21 +334,74 @@ class AccessTokenTest(BaseOAuth2TestCase):
|
|||
self.assertEqual('invalid_grant', json.loads(response.content)['error'],
|
||||
response.content)
|
||||
|
||||
def test_password_grant(self):
|
||||
def test_password_grant_public(self):
|
||||
c = self.get_client()
|
||||
c.client_type = 1 # public
|
||||
c.save()
|
||||
|
||||
response = self.client.post(self.access_token_url(), {
|
||||
'grant_type': 'password',
|
||||
'client_id': self.get_client().client_id,
|
||||
'client_secret': self.get_client().client_secret,
|
||||
'client_id': c.client_id,
|
||||
# No secret needed
|
||||
'username': self.get_user().username,
|
||||
'password': self.get_password(),
|
||||
})
|
||||
|
||||
self.assertEqual(200, response.status_code, response.content)
|
||||
|
||||
def test_password_grant_confidential(self):
|
||||
c = self.get_client()
|
||||
c.client_type = 0 # confidential
|
||||
c.save()
|
||||
|
||||
response = self.client.post(self.access_token_url(), {
|
||||
'grant_type': 'password',
|
||||
'client_id': self.get_client().client_id,
|
||||
'client_secret': self.get_client().client_secret,
|
||||
'client_id': c.client_id,
|
||||
'client_secret': c.client_secret,
|
||||
'username': self.get_user().username,
|
||||
'password': self.get_password(),
|
||||
})
|
||||
|
||||
self.assertEqual(200, response.status_code, response.content)
|
||||
|
||||
def test_password_grant_confidential_no_secret(self):
|
||||
c = self.get_client()
|
||||
c.client_type = 0 # confidential
|
||||
c.save()
|
||||
|
||||
response = self.client.post(self.access_token_url(), {
|
||||
'grant_type': 'password',
|
||||
'client_id': c.client_id,
|
||||
'username': self.get_user().username,
|
||||
'password': self.get_password(),
|
||||
})
|
||||
|
||||
self.assertEqual('invalid_client', json.loads(response.content)['error'])
|
||||
|
||||
def test_password_grant_invalid_password_public(self):
|
||||
c = self.get_client()
|
||||
c.client_type = 1 # public
|
||||
c.save()
|
||||
|
||||
response = self.client.post(self.access_token_url(), {
|
||||
'grant_type': 'password',
|
||||
'client_id': c.client_id,
|
||||
'username': self.get_user().username,
|
||||
'password': self.get_password() + 'invalid',
|
||||
})
|
||||
|
||||
self.assertEqual(400, response.status_code, response.content)
|
||||
self.assertEqual('invalid_client', json.loads(response.content)['error'])
|
||||
|
||||
def test_password_grant_invalid_password_confidential(self):
|
||||
c = self.get_client()
|
||||
c.client_type = 0 # confidential
|
||||
c.save()
|
||||
|
||||
response = self.client.post(self.access_token_url(), {
|
||||
'grant_type': 'password',
|
||||
'client_id': c.client_id,
|
||||
'client_secret': c.client_secret,
|
||||
'username': self.get_user().username,
|
||||
'password': self.get_password() + 'invalid',
|
||||
})
|
||||
|
|
|
@ -7,7 +7,7 @@ from .forms import AuthorizationRequestForm, AuthorizationForm
|
|||
from .forms import PasswordGrantForm, RefreshTokenGrantForm
|
||||
from .forms import AuthorizationCodeGrantForm
|
||||
from .models import Client, RefreshToken, AccessToken
|
||||
from .backends import BasicClientBackend, RequestParamsClientBackend
|
||||
from .backends import BasicClientBackend, RequestParamsClientBackend, PublicPasswordBackend
|
||||
|
||||
|
||||
class Capture(Capture):
|
||||
|
@ -70,6 +70,7 @@ class AccessTokenView(AccessTokenView):
|
|||
authentication = (
|
||||
BasicClientBackend,
|
||||
RequestParamsClientBackend,
|
||||
PublicPasswordBackend,
|
||||
)
|
||||
|
||||
def get_authorization_code_grant(self, request, data, client):
|
||||
|
|
Loading…
Reference in New Issue