summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJoel Stevenson <jstevenson@bepress.com>2016-11-21 12:43:13 -0800
committerJoel Stevenson <jstevenson@bepress.com>2016-11-21 13:59:39 -0800
commitf1a8fc40e117511c790bc63d00a19a90058e1ce1 (patch)
tree19737f1eddb403c9e94d10669fd800076c226bba
parent67f973ff7f98bb3d892a33eda67ba1dab3bddead (diff)
downloadoauthlib-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.py5
-rw-r--r--oauthlib/oauth2/rfc6749/endpoints/token.py9
-rw-r--r--oauthlib/oauth2/rfc6749/grant_types/openid_connect.py9
-rw-r--r--oauthlib/oauth2/rfc6749/tokens.py3
-rw-r--r--tests/oauth2/rfc6749/endpoints/test_scope_handling.py57
-rw-r--r--tests/oauth2/rfc6749/test_server.py27
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)