Visitar URL original
Merge pull request #920 from hekhuisk/validate-client-authentication · oauthlib/oauthlib@1b68cea · GitHub
Skip to content

Commit 1b68cea

Browse files
authored
Merge pull request #920 from hekhuisk/validate-client-authentication
Fix client authentication for DeviceCodeGrant when getting a token
2 parents 9859b05 + c951a1d commit 1b68cea

18 files changed

Lines changed: 170 additions & 96 deletions

File tree

‎docs/oauth2/server.rst‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -300,7 +300,8 @@ can be seen below.
300300
301301
def validate_client_id(self, client_id, request):
302302
try:
303-
Client.objects.get(client_id=client_id)
303+
client = Client.objects.get(client_id=client_id)
304+
request.client = client
304305
return True
305306
except Client.DoesNotExist:
306307
return False

‎oauthlib/oauth2/__init__.py‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,5 +66,6 @@
6666
from .rfc6749.tokens import BearerToken, OAuth2Token
6767
from .rfc6749.utils import is_secure_transport
6868
from .rfc8628.clients import DeviceClient
69-
from oauthlib.oauth2.rfc8628.endpoints import DeviceAuthorizationEndpoint, DeviceApplicationServer
70-
from oauthlib.oauth2.rfc8628.grant_types import DeviceCodeGrant
69+
from .rfc8628.endpoints import DeviceApplicationServer, DeviceAuthorizationEndpoint
70+
from .rfc8628.errors import AccessDenied, AuthorizationPendingError, ExpiredTokenError, SlowDownError
71+
from .rfc8628.grant_types import DeviceCodeGrant

‎oauthlib/oauth2/rfc6749/grant_types/authorization_code.py‎

Lines changed: 1 addition & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -452,28 +452,7 @@ def validate_token_request(self, request):
452452
raise errors.InvalidRequestError(description='Duplicate %s parameter.' % param,
453453
request=request)
454454

455-
if self.request_validator.client_authentication_required(request):
456-
# If the client type is confidential or the client was issued client
457-
# credentials (or assigned other authentication requirements), the
458-
# client MUST authenticate with the authorization server as described
459-
# in Section 3.2.1.
460-
# https://tools.ietf.org/html/rfc6749#section-3.2.1
461-
if not self.request_validator.authenticate_client(request):
462-
log.debug('Client authentication failed, %r.', request)
463-
raise errors.InvalidClientError(request=request)
464-
elif not self.request_validator.authenticate_client_id(request.client_id, request):
465-
# REQUIRED, if the client is not authenticating with the
466-
# authorization server as described in Section 3.2.1.
467-
# https://tools.ietf.org/html/rfc6749#section-3.2.1
468-
log.debug('Client authentication failed, %r.', request)
469-
raise errors.InvalidClientError(request=request)
470-
471-
if not hasattr(request.client, 'client_id'):
472-
raise NotImplementedError('Authenticate client must set the '
473-
'request.client.client_id attribute '
474-
'in authenticate_client.')
475-
476-
request.client_id = request.client_id or request.client.client_id
455+
self.validate_client_authentication(request)
477456

478457
# Ensure client is authorized use of this grant type
479458
self.validate_grant_type(request)

‎oauthlib/oauth2/rfc6749/grant_types/base.py‎

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,57 @@ def validate_scopes(self, request):
175175
request.scopes, request.client, request):
176176
raise errors.InvalidScopeError(request=request)
177177

178+
def validate_client_confidential(self, request):
179+
# If the client type is confidential or the client was issued client
180+
# credentials (or assigned other authentication requirements), the
181+
# client MUST authenticate with the authorization server as described
182+
# in Section 3.2.1.
183+
# https://tools.ietf.org/html/rfc6749#section-3.2.1
184+
log.debug('Authenticating confidential client, %r.', request)
185+
if not self.request_validator.authenticate_client(request):
186+
log.debug('Client authentication failed, %r.', request)
187+
raise errors.InvalidClientError(request=request)
188+
189+
if request.client is None or \
190+
not hasattr(request.client, 'client_id'):
191+
raise NotImplementedError('Authenticate client must set the '
192+
'request.client.client_id attribute '
193+
'in authenticate_client.')
194+
195+
if not request.client_id:
196+
request.client_id = request.client.client_id
197+
elif request.client_id != request.client.client_id:
198+
raise errors.ServerError('Discrepency found between client identifier')
199+
200+
def validate_client_public(self, request):
201+
# REQUIRED, if the client is not authenticating with the
202+
# authorization server as described in Section 3.2.1.
203+
# https://tools.ietf.org/html/rfc6749#section-3.2.1
204+
log.debug('Authenticating public client, %r.', request)
205+
if not self.request_validator.authenticate_client_id(request.client_id, request):
206+
log.debug('Client authentication failed, %r.', request)
207+
raise errors.InvalidClientError(request=request)
208+
209+
if request.client is None or \
210+
not hasattr(request.client, 'client_id'):
211+
raise NotImplementedError('Authenticate client must set the '
212+
'request.client.client_id attribute '
213+
'in authenticate_client.')
214+
215+
if not request.client_id:
216+
request.client_id = request.client.client_id
217+
elif request.client_id != request.client.client_id:
218+
raise errors.ServerError('Discrepency found between client identifier')
219+
220+
def validate_client_authentication(self, request):
221+
"""Raise on failed client authentication."""
222+
# Handles confidential clients
223+
if self.request_validator.client_authentication_required(request):
224+
self.validate_client_confidential(request)
225+
# Handles public clients
226+
else:
227+
self.validate_client_public(request)
228+
178229
def prepare_authorization_response(self, request, token, headers, body, status):
179230
"""Place token according to response mode.
180231

‎oauthlib/oauth2/rfc6749/grant_types/client_credentials.py‎

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -103,18 +103,11 @@ def validate_token_request(self, request):
103103
raise errors.InvalidRequestError(description='Duplicate %s parameter.' % param,
104104
request=request)
105105

106-
log.debug('Authenticating client, %r.', request)
107-
if not self.request_validator.authenticate_client(request):
108-
log.debug('Client authentication failed, %r.', request)
109-
raise errors.InvalidClientError(request=request)
110-
elif not hasattr(request.client, 'client_id'):
111-
raise NotImplementedError('Authenticate client must set the '
112-
'request.client.client_id attribute '
113-
'in authenticate_client.')
106+
self.validate_client_confidential(request)
107+
114108
# Ensure client is authorized use of this grant type
115109
self.validate_grant_type(request)
116110

117-
request.client_id = request.client_id or request.client.client_id
118111
log.debug('Authorizing access to client %r.', request.client_id)
119112
self.validate_scopes(request)
120113

‎oauthlib/oauth2/rfc6749/grant_types/refresh_token.py‎

Lines changed: 2 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -91,22 +91,8 @@ def validate_token_request(self, request):
9191

9292
# Because refresh tokens are typically long-lasting credentials used to
9393
# request additional access tokens, the refresh token is bound to the
94-
# client to which it was issued. If the client type is confidential or
95-
# the client was issued client credentials (or assigned other
96-
# authentication requirements), the client MUST authenticate with the
97-
# authorization server as described in Section 3.2.1.
98-
# https://tools.ietf.org/html/rfc6749#section-3.2.1
99-
if self.request_validator.client_authentication_required(request):
100-
log.debug('Authenticating client, %r.', request)
101-
if not self.request_validator.authenticate_client(request):
102-
log.debug('Invalid client (%r), denying access.', request)
103-
raise errors.InvalidClientError(request=request)
104-
# Ensure that request.client_id is set.
105-
if request.client_id is None and request.client is not None:
106-
request.client_id = request.client.client_id
107-
elif not self.request_validator.authenticate_client_id(request.client_id, request):
108-
log.debug('Client authentication failed, %r.', request)
109-
raise errors.InvalidClientError(request=request)
94+
# client to which it was issued
95+
self.validate_client_authentication(request)
11096

11197
# Ensure client is authorized use of this grant type
11298
self.validate_grant_type(request)

‎oauthlib/oauth2/rfc6749/grant_types/resource_owner_password_credentials.py‎

Lines changed: 2 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -85,14 +85,6 @@ def create_token_response(self, request, token_handler):
8585
"""
8686
headers = self._get_default_headers()
8787
try:
88-
if self.request_validator.client_authentication_required(request):
89-
log.debug('Authenticating client, %r.', request)
90-
if not self.request_validator.authenticate_client(request):
91-
log.debug('Client authentication failed, %r.', request)
92-
raise errors.InvalidClientError(request=request)
93-
elif not self.request_validator.authenticate_client_id(request.client_id, request):
94-
log.debug('Client authentication failed, %r.', request)
95-
raise errors.InvalidClientError(request=request)
9688
log.debug('Validating access token request, %r.', request)
9789
self.validate_token_request(request)
9890
except errors.OAuth2Error as e:
@@ -175,23 +167,18 @@ def validate_token_request(self, request):
175167
if not request.grant_type == 'password':
176168
raise errors.UnsupportedGrantTypeError(request=request)
177169

170+
self.validate_client_authentication(request)
171+
178172
log.debug('Validating username %s.', request.username)
179173
if not self.request_validator.validate_user(request.username,
180174
request.password, request.client, request):
181175
raise errors.InvalidGrantError(
182176
'Invalid credentials given.', request=request)
183-
elif not hasattr(request.client, 'client_id'):
184-
raise NotImplementedError(
185-
'Validate user must set the '
186-
'request.client.client_id attribute '
187-
'in authenticate_client.')
188177
log.debug('Authorizing access to user %r.', request.user)
189178

190179
# Ensure client is authorized use of this grant type
191180
self.validate_grant_type(request)
192181

193-
if request.client:
194-
request.client_id = request.client_id or request.client.client_id
195182
self.validate_scopes(request)
196183

197184
for validator in self.custom_validators.post_token:

‎oauthlib/oauth2/rfc6749/request_validator.py‎

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,11 @@ def authenticate_client(self, request, *args, **kwargs):
6060
class must contain the 'client_id' attribute and the 'client_id' must have
6161
a value.
6262
63+
This method MUST NOT be used to authenticate public clients. Public
64+
clients do not authenticate through means outside the OAuth 2 spec and
65+
are instead validated by :meth:`authenticate_client_id`. If the client
66+
is public, this method must return False.
67+
6368
:param request: OAuthlib request.
6469
:type request: oauthlib.common.Request
6570
:rtype: True or False
@@ -69,6 +74,7 @@ class must contain the 'client_id' attribute and the 'client_id' must have
6974
- Resource Owner Password Credentials Grant (may be disabled)
7075
- Client Credentials Grant
7176
- Refresh Token Grant
77+
- Device Code Grant
7278
7379
.. _`HTTP Basic Authentication Scheme`: https://tools.ietf.org/html/rfc1945#section-11.1
7480
"""
@@ -80,9 +86,10 @@ def authenticate_client_id(self, client_id, request, *args, **kwargs):
8086
A non-confidential client is one that is not required to authenticate
8187
through other means, such as using HTTP Basic.
8288
83-
Note, while not strictly necessary it can often be very convenient
84-
to set request.client to the client object associated with the
85-
given client_id.
89+
After the client identification succeeds, this method needs to set the
90+
client on the request, i.e. request.client = client. A client object's
91+
class must contain the 'client_id' attribute and the 'client_id' must have
92+
a value.
8693
8794
:param client_id: Unicode client identifier.
8895
:param request: OAuthlib request.
@@ -91,6 +98,9 @@ def authenticate_client_id(self, client_id, request, *args, **kwargs):
9198
9299
Method is used by:
93100
- Authorization Code Grant
101+
- Resource Owner Password Credentials Grant
102+
- Refresh Token Grant
103+
- Device Code Grant
94104
"""
95105
raise NotImplementedError('Subclasses must implement this method.')
96106

@@ -419,9 +429,10 @@ def validate_bearer_token(self, token, scopes, request):
419429
def validate_client_id(self, client_id, request, *args, **kwargs):
420430
"""Ensure client_id belong to a valid and active client.
421431
422-
Note, while not strictly necessary it can often be very convenient
423-
to set request.client to the client object associated with the
424-
given client_id.
432+
After the client identification succeeds, this method needs to set the
433+
client on the request, i.e. request.client = client. A client object's
434+
class must contain the 'client_id' attribute and the 'client_id' must have
435+
a value.
425436
426437
:param client_id: Unicode client identifier.
427438
:param request: OAuthlib request.

‎oauthlib/oauth2/rfc8628/grant_types/device_code.py‎

Lines changed: 1 addition & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -55,19 +55,11 @@ def validate_token_request(self, request: common.Request) -> None:
5555
description=f"Duplicate {param} parameter.", request=request
5656
)
5757

58-
if not self.request_validator.authenticate_client(request):
59-
raise rfc6749_errors.InvalidClientError(request=request)
60-
elif not hasattr(request.client, "client_id"):
61-
raise NotImplementedError(
62-
"Authenticate client must set the "
63-
"request.client.client_id attribute "
64-
"in authenticate_client."
65-
)
58+
self.validate_client_authentication(request)
6659

6760
# Ensure client is authorized use of this grant type
6861
self.validate_grant_type(request)
6962

70-
request.client_id = request.client_id or request.client.client_id
7163
self.validate_scopes(request)
7264

7365
for validator in self.custom_validators.post_token:
@@ -93,13 +85,7 @@ def create_token_response(
9385
"""
9486
headers = self._get_default_headers()
9587
try:
96-
if self.request_validator.client_authentication_required(
97-
request
98-
) and not self.request_validator.authenticate_client(request):
99-
raise rfc6749_errors.InvalidClientError(request=request)
100-
10188
self.validate_token_request(request)
102-
10389
except rfc6749_errors.OAuth2Error as e:
10490
headers.update(e.headers)
10591
return headers, e.json, e.status_code

‎tests/oauth2/rfc6749/endpoints/test_error_responses.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -400,7 +400,7 @@ def test_invalid_client(self):
400400
self.assertEqual('invalid_client', json.loads(body)['error'])
401401

402402
# Client credentials grant
403-
_, body, _ = self.legacy.create_token_response('https://i.b/token',
403+
_, body, _ = self.backend.create_token_response('https://i.b/token',
404404
body='grant_type=client_credentials')
405405
self.assertEqual('invalid_client', json.loads(body)['error'])
406406

0 commit comments

Comments
 (0)