From f7c91671aa66b03aca22162564df65079c963fa4 Mon Sep 17 00:00:00 2001 From: Robert Raposa Date: Mon, 10 Apr 2023 13:18:36 -0400 Subject: [PATCH 1/4] feat: allow for forcing asymmetric jwts Add a temporary feature toggle to force the LMS to only produce asymmetric JWTs. This is a part of DEPR of Symmetric JWTs: https://github.com/openedx/public-engineering/issues/83 --- openedx/core/djangoapps/oauth_dispatch/jwt.py | 21 ++++++++++++++++++- .../oauth_dispatch/tests/test_jwt.py | 5 +++++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/openedx/core/djangoapps/oauth_dispatch/jwt.py b/openedx/core/djangoapps/oauth_dispatch/jwt.py index feabcaa13aa7..eeb1bb72bdbf 100644 --- a/openedx/core/djangoapps/oauth_dispatch/jwt.py +++ b/openedx/core/djangoapps/oauth_dispatch/jwt.py @@ -6,8 +6,9 @@ from time import time from django.conf import settings -from edx_django_utils.monitoring import set_custom_attribute +from edx_django_utils.monitoring import increment, set_custom_attribute from edx_rbac.utils import create_role_auth_claim_for_user +from edx_toggles.toggles import SettingToggle from jwkest import jwk from jwkest.jws import JWS @@ -159,6 +160,9 @@ def _create_jwt( secret (string): Overrides configured JWT secret (signing) key. """ use_asymmetric_key = _get_use_asymmetric_key_value(is_restricted, use_asymmetric_key) + # Enable monitoring of key type used. Use increment in case there are multiple calls in a transaction. + increment('use_asymmetric_key_count') if use_asymmetric_key else increment('use_symmetric_key_count') + # Default scopes should only contain non-privileged data. # Do not be misled by the fact that `email` and `profile` are default scopes. They # were included for legacy compatibility, even though they contain privileged data. @@ -188,10 +192,25 @@ def _create_jwt( return _encode_and_sign(payload, use_asymmetric_key, secret) +# .. toggle_name: FORCE_USE_ASYMMETRIC_KEY +# .. toggle_implementation: SettingToggle +# .. toggle_default: False +# .. toggle_description: When True, forces the LMS to only create JWTs signed with the asymmetric +# key. This is a temporary rollout toggle for DEPR of symmetic JWTs. +# .. toggle_use_cases: temporary +# .. toggle_creation_date: 2023-04-10 +# .. toggle_target_removal_date: 2023-07-31 +# .. toggle_tickets: https://github.com/openedx/public-engineering/issues/83 +FORCE_USE_ASYMMETRIC_KEY = SettingToggle('FORCE_USE_ASYMMETRIC_KEY', default=False, module_name=__name__) + + def _get_use_asymmetric_key_value(is_restricted, use_asymmetric_key): """ Returns the value to use for use_asymmetric_key. """ + if FORCE_USE_ASYMMETRIC_KEY.is_enabled(): + return True + return use_asymmetric_key or is_restricted diff --git a/openedx/core/djangoapps/oauth_dispatch/tests/test_jwt.py b/openedx/core/djangoapps/oauth_dispatch/tests/test_jwt.py index d4cbe860541e..9e9c811be56b 100644 --- a/openedx/core/djangoapps/oauth_dispatch/tests/test_jwt.py +++ b/openedx/core/djangoapps/oauth_dispatch/tests/test_jwt.py @@ -73,6 +73,11 @@ def test_dot_create_jwt_for_token_with_asymmetric(self): jwt_token = self._create_jwt_for_token(DOTAdapter(), use_asymmetric_key=True) self._assert_jwt_is_valid(jwt_token, should_be_asymmetric_key=True) + @override_settings(FORCE_USE_ASYMMETRIC_KEY=True) + def test_dot_create_jwt_for_token_forced_asymmetric(self): + jwt_token = self._create_jwt_for_token(DOTAdapter(), use_asymmetric_key=False) + self._assert_jwt_is_valid(jwt_token, should_be_asymmetric_key=True) + def test_create_jwt_for_token_default_expire_seconds(self): oauth_adapter = DOTAdapter() jwt_token = self._create_jwt_for_token(oauth_adapter, use_asymmetric_key=False) From 1091e366b0140dc6381725affcb0b750be6f34c6 Mon Sep 17 00:00:00 2001 From: Robert Raposa Date: Mon, 10 Apr 2023 13:36:09 -0400 Subject: [PATCH 2/4] fixup! update name --- openedx/core/djangoapps/oauth_dispatch/jwt.py | 10 ++++++---- .../core/djangoapps/oauth_dispatch/tests/test_jwt.py | 2 +- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/openedx/core/djangoapps/oauth_dispatch/jwt.py b/openedx/core/djangoapps/oauth_dispatch/jwt.py index eeb1bb72bdbf..d8ff59284829 100644 --- a/openedx/core/djangoapps/oauth_dispatch/jwt.py +++ b/openedx/core/djangoapps/oauth_dispatch/jwt.py @@ -161,7 +161,7 @@ def _create_jwt( """ use_asymmetric_key = _get_use_asymmetric_key_value(is_restricted, use_asymmetric_key) # Enable monitoring of key type used. Use increment in case there are multiple calls in a transaction. - increment('use_asymmetric_key_count') if use_asymmetric_key else increment('use_symmetric_key_count') + increment('create_asymmetric_jwt_count') if use_asymmetric_key else increment('create_symmetric_jwt_count') # Default scopes should only contain non-privileged data. # Do not be misled by the fact that `email` and `profile` are default scopes. They @@ -192,7 +192,7 @@ def _create_jwt( return _encode_and_sign(payload, use_asymmetric_key, secret) -# .. toggle_name: FORCE_USE_ASYMMETRIC_KEY +# .. toggle_name: JWT_AUTH_FORCE_CREATE_ASYMMETRIC # .. toggle_implementation: SettingToggle # .. toggle_default: False # .. toggle_description: When True, forces the LMS to only create JWTs signed with the asymmetric @@ -201,14 +201,16 @@ def _create_jwt( # .. toggle_creation_date: 2023-04-10 # .. toggle_target_removal_date: 2023-07-31 # .. toggle_tickets: https://github.com/openedx/public-engineering/issues/83 -FORCE_USE_ASYMMETRIC_KEY = SettingToggle('FORCE_USE_ASYMMETRIC_KEY', default=False, module_name=__name__) +JWT_AUTH_FORCE_CREATE_ASYMMETRIC = SettingToggle( + 'JWT_AUTH_FORCE_CREATE_ASYMMETRIC', default=False, module_name=__name__ +) def _get_use_asymmetric_key_value(is_restricted, use_asymmetric_key): """ Returns the value to use for use_asymmetric_key. """ - if FORCE_USE_ASYMMETRIC_KEY.is_enabled(): + if JWT_AUTH_FORCE_CREATE_ASYMMETRIC.is_enabled(): return True return use_asymmetric_key or is_restricted diff --git a/openedx/core/djangoapps/oauth_dispatch/tests/test_jwt.py b/openedx/core/djangoapps/oauth_dispatch/tests/test_jwt.py index 9e9c811be56b..abacfe6bee8a 100644 --- a/openedx/core/djangoapps/oauth_dispatch/tests/test_jwt.py +++ b/openedx/core/djangoapps/oauth_dispatch/tests/test_jwt.py @@ -73,7 +73,7 @@ def test_dot_create_jwt_for_token_with_asymmetric(self): jwt_token = self._create_jwt_for_token(DOTAdapter(), use_asymmetric_key=True) self._assert_jwt_is_valid(jwt_token, should_be_asymmetric_key=True) - @override_settings(FORCE_USE_ASYMMETRIC_KEY=True) + @override_settings(JWT_AUTH_FORCE_CREATE_ASYMMETRIC=True) def test_dot_create_jwt_for_token_forced_asymmetric(self): jwt_token = self._create_jwt_for_token(DOTAdapter(), use_asymmetric_key=False) self._assert_jwt_is_valid(jwt_token, should_be_asymmetric_key=True) From 0122beefa6ec48e3b43bd2004d3f398093912fa2 Mon Sep 17 00:00:00 2001 From: Robert Raposa Date: Mon, 10 Apr 2023 13:39:31 -0400 Subject: [PATCH 3/4] fixup! fix typo --- openedx/core/djangoapps/oauth_dispatch/jwt.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openedx/core/djangoapps/oauth_dispatch/jwt.py b/openedx/core/djangoapps/oauth_dispatch/jwt.py index d8ff59284829..d3c77aba1ff5 100644 --- a/openedx/core/djangoapps/oauth_dispatch/jwt.py +++ b/openedx/core/djangoapps/oauth_dispatch/jwt.py @@ -196,7 +196,7 @@ def _create_jwt( # .. toggle_implementation: SettingToggle # .. toggle_default: False # .. toggle_description: When True, forces the LMS to only create JWTs signed with the asymmetric -# key. This is a temporary rollout toggle for DEPR of symmetic JWTs. +# key. This is a temporary rollout toggle for DEPR of symmetric JWTs. # .. toggle_use_cases: temporary # .. toggle_creation_date: 2023-04-10 # .. toggle_target_removal_date: 2023-07-31 From 820de197b94ef9d339318997bc9902a0c4725d3f Mon Sep 17 00:00:00 2001 From: Robert Raposa Date: Mon, 10 Apr 2023 14:18:38 -0400 Subject: [PATCH 4/4] fixup! use real if --- openedx/core/djangoapps/oauth_dispatch/jwt.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/openedx/core/djangoapps/oauth_dispatch/jwt.py b/openedx/core/djangoapps/oauth_dispatch/jwt.py index d3c77aba1ff5..329d28822fca 100644 --- a/openedx/core/djangoapps/oauth_dispatch/jwt.py +++ b/openedx/core/djangoapps/oauth_dispatch/jwt.py @@ -161,7 +161,10 @@ def _create_jwt( """ use_asymmetric_key = _get_use_asymmetric_key_value(is_restricted, use_asymmetric_key) # Enable monitoring of key type used. Use increment in case there are multiple calls in a transaction. - increment('create_asymmetric_jwt_count') if use_asymmetric_key else increment('create_symmetric_jwt_count') + if use_asymmetric_key: + increment('create_asymmetric_jwt_count') + else: + increment('create_symmetric_jwt_count') # Default scopes should only contain non-privileged data. # Do not be misled by the fact that `email` and `profile` are default scopes. They