summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJonathan Huot <JonathanHuot@users.noreply.github.com>2021-08-12 09:11:06 +0200
committerGitHub <noreply@github.com>2021-08-12 09:11:06 +0200
commit555e3b06022c32e420b3bc0709c66988e91b7670 (patch)
treef9b13648a3e933fa17756fe66d2cad054412bc17
parent4fddf07313aed766b633a6ca400bca67980017ad (diff)
parentf6b625886d03f1582a7a99317e84c57d03895339 (diff)
downloadoauthlib-555e3b06022c32e420b3bc0709c66988e91b7670.tar.gz
Merge pull request #752 from nsklikas/oidc-refresh
Oidc refresh
-rw-r--r--CHANGELOG.rst5
-rw-r--r--docs/oauth2/oidc/refresh_token.rst6
-rw-r--r--oauthlib/oauth2/rfc6749/grant_types/refresh_token.py2
-rw-r--r--oauthlib/openid/connect/core/grant_types/__init__.py1
-rw-r--r--oauthlib/openid/connect/core/grant_types/refresh_token.py34
-rw-r--r--oauthlib/openid/connect/core/request_validator.py12
-rw-r--r--tests/openid/connect/core/grant_types/test_refresh_token.py105
7 files changed, 164 insertions, 1 deletions
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index 79f241d..21c9159 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -7,6 +7,9 @@ OAuth2.0 Provider - Bugfixes
* #753: Fix acceptance of valid IPv6 addresses in URI validation
+OAuth2.0 Provider - Features
+ * #751: OIDC add support of refreshing ID Tokens
+
OAuth2.0 Client - Bugfixes
* #730: Base OAuth2 Client now has a consistent way of managing the `scope`: it consistently
@@ -25,6 +28,8 @@ OAuth2.0 Provider - Bugfixes
* #746: OpenID Connect Hybrid - fix nonce not passed to add_id_token
* #756: Different prompt values are now handled according to spec (e.g. prompt=none)
* #759: OpenID Connect - fix Authorization: Basic parsing
+ * #751: The RefreshTokenGrant modifiers now take the same arguments as the
+ AuthorizationCodeGrant modifiers (`token`, `token_handler`, `request`).
General
* #716: improved skeleton validator for public vs private client
diff --git a/docs/oauth2/oidc/refresh_token.rst b/docs/oauth2/oidc/refresh_token.rst
new file mode 100644
index 0000000..01d2d7f
--- /dev/null
+++ b/docs/oauth2/oidc/refresh_token.rst
@@ -0,0 +1,6 @@
+OpenID Authorization Code
+-------------------------
+
+.. autoclass:: oauthlib.openid.connect.core.grant_types.RefreshTokenGrant
+ :members:
+ :inherited-members:
diff --git a/oauthlib/oauth2/rfc6749/grant_types/refresh_token.py b/oauthlib/oauth2/rfc6749/grant_types/refresh_token.py
index 8698a3d..f801de4 100644
--- a/oauthlib/oauth2/rfc6749/grant_types/refresh_token.py
+++ b/oauthlib/oauth2/rfc6749/grant_types/refresh_token.py
@@ -63,7 +63,7 @@ class RefreshTokenGrant(GrantTypeBase):
refresh_token=self.issue_new_refresh_tokens)
for modifier in self._token_modifiers:
- token = modifier(token)
+ token = modifier(token, token_handler, request)
self.request_validator.save_token(token, request)
diff --git a/oauthlib/openid/connect/core/grant_types/__init__.py b/oauthlib/openid/connect/core/grant_types/__init__.py
index 887a585..8dad5f6 100644
--- a/oauthlib/openid/connect/core/grant_types/__init__.py
+++ b/oauthlib/openid/connect/core/grant_types/__init__.py
@@ -10,3 +10,4 @@ from .dispatchers import (
)
from .hybrid import HybridGrant
from .implicit import ImplicitGrant
+from .refresh_token import RefreshTokenGrant
diff --git a/oauthlib/openid/connect/core/grant_types/refresh_token.py b/oauthlib/openid/connect/core/grant_types/refresh_token.py
new file mode 100644
index 0000000..43e4499
--- /dev/null
+++ b/oauthlib/openid/connect/core/grant_types/refresh_token.py
@@ -0,0 +1,34 @@
+"""
+oauthlib.openid.connect.core.grant_types
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+"""
+import logging
+
+from oauthlib.oauth2.rfc6749.grant_types.refresh_token import (
+ RefreshTokenGrant as OAuth2RefreshTokenGrant,
+)
+
+from .base import GrantTypeBase
+
+log = logging.getLogger(__name__)
+
+
+class RefreshTokenGrant(GrantTypeBase):
+
+ def __init__(self, request_validator=None, **kwargs):
+ self.proxy_target = OAuth2RefreshTokenGrant(
+ request_validator=request_validator, **kwargs)
+ self.register_token_modifier(self.add_id_token)
+
+ def add_id_token(self, token, token_handler, request):
+ """
+ Construct an initial version of id_token, and let the
+ request_validator sign or encrypt it.
+
+ The authorization_code version of this method is used to
+ retrieve the nonce accordingly to the code storage.
+ """
+ if not self.request_validator.refresh_id_token(request):
+ return token
+
+ return super().add_id_token(token, token_handler, request)
diff --git a/oauthlib/openid/connect/core/request_validator.py b/oauthlib/openid/connect/core/request_validator.py
index e8f334b..47c4cd9 100644
--- a/oauthlib/openid/connect/core/request_validator.py
+++ b/oauthlib/openid/connect/core/request_validator.py
@@ -306,3 +306,15 @@ class RequestValidator(OAuth2RequestValidator):
Method is used by:
UserInfoEndpoint
"""
+
+ def refresh_id_token(self, request):
+ """Whether the id token should be refreshed. Default, True
+
+ :param request: OAuthlib request.
+ :type request: oauthlib.common.Request
+ :rtype: True or False
+
+ Method is used by:
+ RefreshTokenGrant
+ """
+ return True
diff --git a/tests/openid/connect/core/grant_types/test_refresh_token.py b/tests/openid/connect/core/grant_types/test_refresh_token.py
new file mode 100644
index 0000000..8126e1b
--- /dev/null
+++ b/tests/openid/connect/core/grant_types/test_refresh_token.py
@@ -0,0 +1,105 @@
+import json
+from unittest import mock
+
+from oauthlib.common import Request
+from oauthlib.oauth2.rfc6749.tokens import BearerToken
+from oauthlib.openid.connect.core.grant_types import RefreshTokenGrant
+
+from tests.oauth2.rfc6749.grant_types.test_refresh_token import (
+ RefreshTokenGrantTest,
+)
+from tests.unittest import TestCase
+
+
+def get_id_token_mock(token, token_handler, request):
+ return "MOCKED_TOKEN"
+
+
+class OpenIDRefreshTokenInterferenceTest(RefreshTokenGrantTest):
+ """Test that OpenID don't interfere with normal OAuth 2 flows."""
+
+ def setUp(self):
+ super().setUp()
+ self.auth = RefreshTokenGrant(request_validator=self.mock_validator)
+
+
+class OpenIDRefreshTokenTest(TestCase):
+
+ def setUp(self):
+ self.request = Request('http://a.b/path')
+ self.request.grant_type = 'refresh_token'
+ self.request.refresh_token = 'lsdkfhj230'
+ self.request.scope = ('hello', 'openid')
+ self.mock_validator = mock.MagicMock()
+
+ self.mock_validator = mock.MagicMock()
+ self.mock_validator.authenticate_client.side_effect = self.set_client
+ self.mock_validator.get_id_token.side_effect = get_id_token_mock
+ self.auth = RefreshTokenGrant(request_validator=self.mock_validator)
+
+ def set_client(self, request):
+ request.client = mock.MagicMock()
+ request.client.client_id = 'mocked'
+ return True
+
+ def test_refresh_id_token(self):
+ self.mock_validator.get_original_scopes.return_value = [
+ 'hello', 'openid'
+ ]
+ bearer = BearerToken(self.mock_validator)
+
+ headers, body, status_code = self.auth.create_token_response(
+ self.request, bearer
+ )
+
+ token = json.loads(body)
+ self.assertEqual(self.mock_validator.save_token.call_count, 1)
+ self.assertIn('access_token', token)
+ self.assertIn('refresh_token', token)
+ self.assertIn('id_token', token)
+ self.assertIn('token_type', token)
+ self.assertIn('expires_in', token)
+ self.assertEqual(token['scope'], 'hello openid')
+ self.mock_validator.refresh_id_token.assert_called_once_with(
+ self.request
+ )
+
+ def test_refresh_id_token_false(self):
+ self.mock_validator.refresh_id_token.return_value = False
+ self.mock_validator.get_original_scopes.return_value = [
+ 'hello', 'openid'
+ ]
+ bearer = BearerToken(self.mock_validator)
+
+ headers, body, status_code = self.auth.create_token_response(
+ self.request, bearer
+ )
+
+ token = json.loads(body)
+ self.assertEqual(self.mock_validator.save_token.call_count, 1)
+ self.assertIn('access_token', token)
+ self.assertIn('refresh_token', token)
+ self.assertIn('token_type', token)
+ self.assertIn('expires_in', token)
+ self.assertEqual(token['scope'], 'hello openid')
+ self.assertNotIn('id_token', token)
+ self.mock_validator.refresh_id_token.assert_called_once_with(
+ self.request
+ )
+
+ def test_refresh_token_without_openid_scope(self):
+ self.request.scope = "hello"
+ bearer = BearerToken(self.mock_validator)
+
+ headers, body, status_code = self.auth.create_token_response(
+ self.request, bearer
+ )
+
+ token = json.loads(body)
+ self.assertEqual(self.mock_validator.save_token.call_count, 1)
+ self.assertIn('access_token', token)
+ self.assertIn('refresh_token', token)
+ self.assertIn('token_type', token)
+ self.assertIn('expires_in', token)
+ self.assertNotIn('id_token', token)
+ self.assertEqual(token['scope'], 'hello')