diff options
author | Joel Stevenson <jstevenson@bepress.com> | 2016-11-21 12:43:13 -0800 |
---|---|---|
committer | Joel Stevenson <jstevenson@bepress.com> | 2016-11-21 13:59:39 -0800 |
commit | f1a8fc40e117511c790bc63d00a19a90058e1ce1 (patch) | |
tree | 19737f1eddb403c9e94d10669fd800076c226bba | |
parent | 67f973ff7f98bb3d892a33eda67ba1dab3bddead (diff) | |
download | oauthlib-f1a8fc40e117511c790bc63d00a19a90058e1ce1.tar.gz |
Normalize handling of request.scopes list
Use the scope_to_list() util to initalize the request.scopes list from
the request.scope request parameter in two place where it was instead
being set to None.
- AuthorizationEndpoint.validate_authorization_request()
- TokenEndpoint.create_token_response()
In both cases the Request should be properly populated before it is
passed to the client's validator.
In the case of the TokenEndpoint - there are OAuth2 workflows that
allow an optional scope parameter so we should have been doing this
for them anyway.
Since scope_to_list() may return None, also update the openid_connect
code to behave properly when this is the case.
Fixes #436
-rw-r--r-- | oauthlib/oauth2/rfc6749/endpoints/authorization.py | 5 | ||||
-rw-r--r-- | oauthlib/oauth2/rfc6749/endpoints/token.py | 9 | ||||
-rw-r--r-- | oauthlib/oauth2/rfc6749/grant_types/openid_connect.py | 9 | ||||
-rw-r--r-- | oauthlib/oauth2/rfc6749/tokens.py | 3 | ||||
-rw-r--r-- | tests/oauth2/rfc6749/endpoints/test_scope_handling.py | 57 | ||||
-rw-r--r-- | tests/oauth2/rfc6749/test_server.py | 27 |
6 files changed, 80 insertions, 30 deletions
diff --git a/oauthlib/oauth2/rfc6749/endpoints/authorization.py b/oauthlib/oauth2/rfc6749/endpoints/authorization.py index adc9f85..de100b1 100644 --- a/oauthlib/oauth2/rfc6749/endpoints/authorization.py +++ b/oauthlib/oauth2/rfc6749/endpoints/authorization.py @@ -10,6 +10,7 @@ from __future__ import absolute_import, unicode_literals import logging from oauthlib.common import Request +from oauthlib.oauth2.rfc6749 import utils from .base import BaseEndpoint, catch_errors_and_unavailability @@ -107,7 +108,9 @@ class AuthorizationEndpoint(BaseEndpoint): """Extract response_type and route to the designated handler.""" request = Request( uri, http_method=http_method, body=body, headers=headers) - request.scopes = None + + request.scopes = utils.scope_to_list(request.scope) + response_type_handler = self.response_types.get( request.response_type, self.default_response_type_handler) return response_type_handler.validate_authorization_request(request) diff --git a/oauthlib/oauth2/rfc6749/endpoints/token.py b/oauthlib/oauth2/rfc6749/endpoints/token.py index c093e1e..96cfbc7 100644 --- a/oauthlib/oauth2/rfc6749/endpoints/token.py +++ b/oauthlib/oauth2/rfc6749/endpoints/token.py @@ -11,6 +11,7 @@ from __future__ import absolute_import, unicode_literals import logging from oauthlib.common import Request +from oauthlib.oauth2.rfc6749 import utils from .base import BaseEndpoint, catch_errors_and_unavailability @@ -91,7 +92,13 @@ class TokenEndpoint(BaseEndpoint): """Extract grant_type and route to the designated handler.""" request = Request( uri, http_method=http_method, body=body, headers=headers) - request.scopes = None + + # 'scope' is an allowed Token Request param in both the "Resource Owner Password Credentials Grant" + # and "Client Credentials Grant" flows + # https://tools.ietf.org/html/rfc6749#section-4.3.2 + # https://tools.ietf.org/html/rfc6749#section-4.4.2 + request.scopes = utils.scope_to_list(request.scope) + request.extra_credentials = credentials if grant_type_for_scope: request.grant_type = grant_type_for_scope diff --git a/oauthlib/oauth2/rfc6749/grant_types/openid_connect.py b/oauthlib/oauth2/rfc6749/grant_types/openid_connect.py index d09893e..6cc3772 100644 --- a/oauthlib/oauth2/rfc6749/grant_types/openid_connect.py +++ b/oauthlib/oauth2/rfc6749/grant_types/openid_connect.py @@ -64,7 +64,7 @@ class AuthCodeGrantDispatcher(object): def _handler_for_request(self, request): handler = self.default_auth_grant - if "openid" in request.scopes: + if request.scopes and "openid" in request.scopes: handler = self.oidc_auth_grant log.debug('Selecting handler for request %r.', handler) @@ -96,7 +96,7 @@ class OpenIDConnectBase(GrantTypeBase): def add_id_token(self, token, token_handler, request): # Treat it as normal OAuth 2 auth code request if openid is not present - if 'openid' not in request.scopes: + if not request.scopes or 'openid' not in request.scopes: return token # Only add an id token on auth/token step if asked for. @@ -249,7 +249,7 @@ class OpenIDConnectBase(GrantTypeBase): """ # Treat it as normal OAuth 2 auth code request if openid is not present - if not 'openid' in request.scopes: + if not request.scopes or 'openid' not in request.scopes: return {} # prompt other than 'none' should be handled by the server code that uses oauthlib @@ -289,7 +289,8 @@ class OpenIDConnectBase(GrantTypeBase): if request.response_type == 'token': return {} - if not 'openid' in request.scopes: + # Treat it as normal OAuth 2 auth code request if openid is not present + if not request.scopes or 'openid' not in request.scopes: return {} # REQUIRED. String value used to associate a Client session with an ID diff --git a/oauthlib/oauth2/rfc6749/tokens.py b/oauthlib/oauth2/rfc6749/tokens.py index 06ff558..2060de1 100644 --- a/oauthlib/oauth2/rfc6749/tokens.py +++ b/oauthlib/oauth2/rfc6749/tokens.py @@ -262,6 +262,9 @@ class BearerToken(TokenBase): 'token_type': 'Bearer', } + # If provided, include - this is optional in some cases https://tools.ietf.org/html/rfc6749#section-3.3 but + # there is currently no mechanism to coordinate issuing a token for only a subset of the requested scopes so + # all tokens issued are for the entire set of requested scopes. if request.scopes is not None: token['scope'] = ' '.join(request.scopes) diff --git a/tests/oauth2/rfc6749/endpoints/test_scope_handling.py b/tests/oauth2/rfc6749/endpoints/test_scope_handling.py index f48a4f9..3c3ee5e 100644 --- a/tests/oauth2/rfc6749/endpoints/test_scope_handling.py +++ b/tests/oauth2/rfc6749/endpoints/test_scope_handling.py @@ -12,7 +12,7 @@ from ....unittest import TestCase from oauthlib.oauth2 import RequestValidator from oauthlib.oauth2 import WebApplicationServer, MobileApplicationServer -from oauthlib.oauth2 import LegacyApplicationServer, BackendApplicationServer +from oauthlib.oauth2 import LegacyApplicationServer, BackendApplicationServer, Server class TestScopeHandling(TestCase): @@ -41,6 +41,7 @@ class TestScopeHandling(TestCase): self.validator = mock.MagicMock(spec=RequestValidator) self.validator.get_default_redirect_uri.return_value = TestScopeHandling.DEFAULT_REDIRECT_URI self.validator.authenticate_client.side_effect = self.set_client + self.server = Server(self.validator) self.web = WebApplicationServer(self.validator) self.mobile = MobileApplicationServer(self.validator) self.legacy = LegacyApplicationServer(self.validator) @@ -50,6 +51,7 @@ class TestScopeHandling(TestCase): scopes = ( ('images', ['images']), ('images+videos', ['images', 'videos']), + ('images+videos+openid', ['images', 'videos', 'openid']), ('http%3A%2f%2fa.b%2fvideos', ['http://a.b/videos']), ('http%3A%2f%2fa.b%2fvideos+pics', ['http://a.b/videos', 'pics']), ('pics+http%3A%2f%2fa.b%2fvideos', ['pics', 'http://a.b/videos']), @@ -64,6 +66,9 @@ class TestScopeHandling(TestCase): scopes, _ = self.mobile.validate_authorization_request( uri % (scope, 'token')) self.assertItemsEqual(scopes, correct_scopes) + scopes, _ = self.server.validate_authorization_request( + uri % (scope, 'code')) + self.assertItemsEqual(scopes, correct_scopes) def test_scope_preservation(self): scope = 'pics+http%3A%2f%2fa.b%2fvideos' @@ -72,36 +77,40 @@ class TestScopeHandling(TestCase): token_uri = 'http://example.com/path' # authorization grant - h, _, s = self.web.create_authorization_response( - auth_uri + 'code', scopes=decoded_scope.split(' ')) - self.validator.validate_code.side_effect = self.set_scopes(decoded_scope.split(' ')) - self.assertEqual(s, 302) - self.assertIn('Location', h) - code = get_query_credentials(h['Location'])['code'][0] - _, body, _ = self.web.create_token_response(token_uri, - body='grant_type=authorization_code&code=%s' % code) - self.assertEqual(json.loads(body)['scope'], decoded_scope) + for backend_server_type in ['web', 'server']: + h, _, s = getattr(self, backend_server_type).create_authorization_response( + auth_uri + 'code', scopes=decoded_scope.split(' ')) + self.validator.validate_code.side_effect = self.set_scopes(decoded_scope.split(' ')) + self.assertEqual(s, 302) + self.assertIn('Location', h) + code = get_query_credentials(h['Location'])['code'][0] + _, body, _ = getattr(self, backend_server_type).create_token_response(token_uri, + body='grant_type=authorization_code&code=%s' % code) + self.assertEqual(json.loads(body)['scope'], decoded_scope) # implicit grant - h, _, s = self.mobile.create_authorization_response( - auth_uri + 'token', scopes=decoded_scope.split(' ')) - self.assertEqual(s, 302) - self.assertIn('Location', h) - self.assertEqual(get_fragment_credentials(h['Location'])['scope'][0], decoded_scope) + for backend_server_type in ['mobile', 'server']: + h, _, s = getattr(self, backend_server_type).create_authorization_response( + auth_uri + 'token', scopes=decoded_scope.split(' ')) + self.assertEqual(s, 302) + self.assertIn('Location', h) + self.assertEqual(get_fragment_credentials(h['Location'])['scope'][0], decoded_scope) # resource owner password credentials grant - body = 'grant_type=password&username=abc&password=secret&scope=%s' + for backend_server_type in ['legacy', 'server']: + body = 'grant_type=password&username=abc&password=secret&scope=%s' - _, body, _ = self.legacy.create_token_response(token_uri, - body=body % scope) - self.assertEqual(json.loads(body)['scope'], decoded_scope) + _, body, _ = getattr(self, backend_server_type).create_token_response(token_uri, + body=body % scope) + self.assertEqual(json.loads(body)['scope'], decoded_scope) # client credentials grant - body = 'grant_type=client_credentials&scope=%s' - self.validator.authenticate_client.side_effect = self.set_user - _, body, _ = self.backend.create_token_response(token_uri, - body=body % scope) - self.assertEqual(json.loads(body)['scope'], decoded_scope) + for backend_server_type in ['backend', 'server']: + body = 'grant_type=client_credentials&scope=%s' + self.validator.authenticate_client.side_effect = self.set_user + _, body, _ = getattr(self, backend_server_type).create_token_response(token_uri, + body=body % scope) + self.assertEqual(json.loads(body)['scope'], decoded_scope) def test_scope_changed(self): scope = 'pics+http%3A%2f%2fa.b%2fvideos' diff --git a/tests/oauth2/rfc6749/test_server.py b/tests/oauth2/rfc6749/test_server.py index fe7edd7..0311a1b 100644 --- a/tests/oauth2/rfc6749/test_server.py +++ b/tests/oauth2/rfc6749/test_server.py @@ -151,6 +151,19 @@ class TokenEndpointTest(TestCase): 'expires_in': self.expires_in, 'access_token': 'abc', 'refresh_token': 'abc', + 'scope': 'all of them', + 'state': 'xyz' + } + self.assertEqual(json.loads(body), token) + + body = 'grant_type=authorization_code&code=abc&state=xyz' + headers, body, status_code = self.endpoint.create_token_response( + '', body=body) + token = { + 'token_type': 'Bearer', + 'expires_in': self.expires_in, + 'access_token': 'abc', + 'refresh_token': 'abc', 'state': 'xyz' } self.assertEqual(json.loads(body), token) @@ -271,6 +284,20 @@ twIDAQAB 'expires_in': self.expires_in, 'access_token': body['access_token'], 'refresh_token': 'abc', + 'scope': u'all of them', + 'state': 'xyz' + } + self.assertEqual(body, token) + + body = 'grant_type=authorization_code&code=abc&state=xyz' + headers, body, status_code = self.endpoint.create_token_response( + '', body=body) + body = json.loads(body) + token = { + 'token_type': 'Bearer', + 'expires_in': self.expires_in, + 'access_token': body['access_token'], + 'refresh_token': 'abc', 'state': 'xyz' } self.assertEqual(body, token) |