From 825931a9b4e591df6248f359d79c5452835ec21a Mon Sep 17 00:00:00 2001 From: Marcos Date: Tue, 7 Jan 2025 10:32:08 -0300 Subject: [PATCH 1/9] chore: Removed edx-token-utils dep and moved necessary logic to the repo --- lms/djangoapps/courseware/jwt.py | 91 +++++++++++++++ lms/djangoapps/courseware/tests/test_jwt.py | 120 ++++++++++++++++++++ lms/djangoapps/courseware/utils.py | 38 +++++++ lms/djangoapps/courseware/views/views.py | 5 +- lms/envs/common.py | 17 ++- 5 files changed, 267 insertions(+), 4 deletions(-) create mode 100644 lms/djangoapps/courseware/jwt.py create mode 100644 lms/djangoapps/courseware/tests/test_jwt.py diff --git a/lms/djangoapps/courseware/jwt.py b/lms/djangoapps/courseware/jwt.py new file mode 100644 index 000000000000..47642b869560 --- /dev/null +++ b/lms/djangoapps/courseware/jwt.py @@ -0,0 +1,91 @@ +""" +JWT Token handling and signing functions. +""" + +import json +from time import time + +from django.conf import settings +from jwkest import Expired, Invalid, MissingKey, jwk +from jwkest.jws import JWS + + +def create_jwt(lms_user_id, expires_in_seconds, additional_token_claims, now=None): + """ + Produce an encoded JWT (string) indicating some temporary permission for the indicated user. + + What permission that is must be encoded in additional_claims. + Arguments: + lms_user_id (int): LMS user ID this token is being generated for + expires_in_seconds (int): Time to token expiry, specified in seconds. + additional_token_claims (dict): Additional claims to include in the token. + now(int): optional now value for testing + """ + now = now or int(time()) + + payload = { + 'lms_user_id': lms_user_id, + 'exp': now + expires_in_seconds, + 'iat': now, + 'iss': settings.TOKEN_SIGNING['JWT_ISSUER'], + 'version': settings.TOKEN_SIGNING['JWT_SUPPORTED_VERSION'], + } + payload.update(additional_token_claims) + return _encode_and_sign(payload) + + +def _encode_and_sign(payload): + """ + Encode and sign the provided payload. + + The signing key and algorithm are pulled from settings. + """ + keys = jwk.KEYS() + + serialized_keypair = json.loads(settings.TOKEN_SIGNING['JWT_PRIVATE_SIGNING_JWK']) + keys.add(serialized_keypair) + algorithm = settings.TOKEN_SIGNING['JWT_SIGNING_ALGORITHM'] + + data = json.dumps(payload) + jws = JWS(data, alg=algorithm) + return jws.sign_compact(keys=keys) + + +def unpack_jwt(token, lms_user_id, now=None): + """ + Unpack and verify an encoded JWT. + + Validate the user and expiration. + + Arguments: + token (string): The token to be unpacked and verified. + lms_user_id (int): LMS user ID this token should match with. + now (int): Optional now value for testing. + + Returns a valid, decoded json payload (string). + """ + now = now or int(time()) + payload = _unpack_and_verify(token) + + if "lms_user_id" not in payload: + raise MissingKey("LMS user id is missing") + if "exp" not in payload: + raise MissingKey("Expiration is missing") + if payload["lms_user_id"] != lms_user_id: + raise Invalid("User does not match") + if payload["exp"] < now: + raise Expired("Token is expired") + + return payload + + +def _unpack_and_verify(token): + """ + Unpack and verify the provided token. + + The signing key and algorithm are pulled from settings. + """ + keys = jwk.KEYS() + keys.load_jwks(settings.TOKEN_SIGNING['JWT_PUBLIC_SIGNING_JWK_SET']) + decoded = JWS().verify_compact(token.encode('utf-8'), keys) + return decoded diff --git a/lms/djangoapps/courseware/tests/test_jwt.py b/lms/djangoapps/courseware/tests/test_jwt.py new file mode 100644 index 000000000000..d21ac1778fb9 --- /dev/null +++ b/lms/djangoapps/courseware/tests/test_jwt.py @@ -0,0 +1,120 @@ +""" +Tests for token handling +""" +import unittest + +from django.conf import settings +from jwkest import BadSignature, Expired, Invalid, MissingKey, jwk +from jwkest.jws import JWS + +from lms.djangoapps.courseware.jwt import _encode_and_sign, create_jwt, unpack_jwt + +import unittest +from unittest.mock import patch + + +test_user_id = 121 +invalid_test_user_id = 120 +test_timeout = 60 +test_now = 1661432902 +test_claims = {"foo": "bar", "baz": "quux", "meaning": 42} +expected_full_token = { + "lms_user_id": test_user_id, + "iat": 1661432902, + "exp": 1661432902 + 60, + "iss": "token-test-issuer", # these lines from test_settings.py + "version": "1.2.0", # these lines from test_settings.py +} + + +class TestSign(unittest.TestCase): + def test_create_jwt(self): + token = create_jwt(test_user_id, test_timeout, {}, test_now) + + decoded = _verify_jwt(token) + self.assertEqual(expected_full_token, decoded) + + def test_create_jwt_with_claims(self): + token = create_jwt(test_user_id, test_timeout, test_claims, test_now) + + expected_token_with_claims = expected_full_token.copy() + expected_token_with_claims.update(test_claims) + + decoded = _verify_jwt(token) + self.assertEqual(expected_token_with_claims, decoded) + + def test_malformed_token(self): + token = create_jwt(test_user_id, test_timeout, test_claims, test_now) + token = token + "a" + + expected_token_with_claims = expected_full_token.copy() + expected_token_with_claims.update(test_claims) + + with self.assertRaises(BadSignature): + _verify_jwt(token) + +def _verify_jwt(jwt_token): + """ + Helper function which verifies the signature and decodes the token + from string back to claims form + """ + keys = jwk.KEYS() + keys.load_jwks(settings.TOKEN_SIGNING['JWT_PUBLIC_SIGNING_JWK_SET']) + decoded = JWS().verify_compact(jwt_token.encode('utf-8'), keys) + return decoded + + +class TestUnpack(unittest.TestCase): + def test_unpack_jwt(self): + token = create_jwt(test_user_id, test_timeout, {}, test_now) + decoded = unpack_jwt(token, test_user_id, test_now) + + self.assertEqual(expected_full_token, decoded) + + def test_unpack_jwt_with_claims(self): + token = create_jwt(test_user_id, test_timeout, test_claims, test_now) + + expected_token_with_claims = expected_full_token.copy() + expected_token_with_claims.update(test_claims) + + decoded = unpack_jwt(token, test_user_id, test_now) + + self.assertEqual(expected_token_with_claims, decoded) + + def test_malformed_token(self): + token = create_jwt(test_user_id, test_timeout, test_claims, test_now) + token = token + "a" + + expected_token_with_claims = expected_full_token.copy() + expected_token_with_claims.update(test_claims) + + with self.assertRaises(BadSignature): + unpack_jwt(token, test_user_id, test_now) + + def test_unpack_token_with_invalid_user(self): + token = create_jwt(invalid_test_user_id, test_timeout, {}, test_now) + + with self.assertRaises(Invalid): + unpack_jwt(token, test_user_id, test_now) + + def test_unpack_expired_token(self): + token = create_jwt(test_user_id, test_timeout, {}, test_now) + + with self.assertRaises(Expired): + unpack_jwt(token, test_user_id, test_now + test_timeout + 1) + + def test_missing_expired_lms_user_id(self): + payload = expected_full_token.copy() + del payload['lms_user_id'] + token = _encode_and_sign(payload) + + with self.assertRaises(MissingKey): + unpack_jwt(token, test_user_id, test_now) + + def test_missing_expired_key(self): + payload = expected_full_token.copy() + del payload['exp'] + token = _encode_and_sign(payload) + + with self.assertRaises(MissingKey): + unpack_jwt(token, test_user_id, test_now) diff --git a/lms/djangoapps/courseware/utils.py b/lms/djangoapps/courseware/utils.py index 5409c89f636b..413e024d281b 100644 --- a/lms/djangoapps/courseware/utils.py +++ b/lms/djangoapps/courseware/utils.py @@ -5,6 +5,8 @@ import hashlib import logging +from time import time + from django.conf import settings from django.http import HttpResponse, HttpResponseBadRequest from edx_rest_api_client.client import OAuthAPIClient @@ -15,6 +17,9 @@ ENROLLMENT_TRACK_PARTITION_ID # lint-amnesty, pylint: disable=wrong-import-order from xmodule.partitions.partitions_service import PartitionService # lint-amnesty, pylint: disable=wrong-import-order +from jwkest import Expired, Invalid, MissingKey, jwk +from jwkest.jws import JWS + from common.djangoapps.course_modes.models import CourseMode from lms.djangoapps.commerce.utils import EcommerceService from lms.djangoapps.courseware.config import ENABLE_NEW_FINANCIAL_ASSISTANCE_FLOW @@ -229,3 +234,36 @@ def _use_new_financial_assistance_flow(course_id): ): return True return False + + + +def unpack_jwt(token, lms_user_id, now=None): + """ + Unpack and verify an encoded JWT. + + Validate the user and expiration. + + Arguments: + token (string): The token to be unpacked and verified. + lms_user_id (int): LMS user ID this token should match with. + now (int): Optional now value for testing. + + Returns a valid, decoded json payload (string). + """ + now = now or int(time()) + + # Unpack and verify token + keys = jwk.KEYS() + keys.load_jwks(settings.TOKEN_SIGNING['JWT_PUBLIC_SIGNING_JWK_SET']) + payload = JWS().verify_compact(token.encode('utf-8'), keys) + + if "lms_user_id" not in payload: + raise MissingKey("LMS user id is missing") + if "exp" not in payload: + raise MissingKey("Expiration is missing") + if payload["lms_user_id"] != lms_user_id: + raise Invalid("User does not match") + if payload["exp"] < now: + raise Expired("Token is expired") + + return payload diff --git a/lms/djangoapps/courseware/views/views.py b/lms/djangoapps/courseware/views/views.py index 6e0804db8ca0..33bd4013f3c7 100644 --- a/lms/djangoapps/courseware/views/views.py +++ b/lms/djangoapps/courseware/views/views.py @@ -46,7 +46,6 @@ from rest_framework.decorators import api_view, throttle_classes from rest_framework.response import Response from rest_framework.throttling import UserRateThrottle -from token_utils.api import unpack_token_for from web_fragments.fragment import Fragment from xmodule.course_block import ( COURSE_VISIBILITY_PUBLIC, @@ -106,7 +105,7 @@ from lms.djangoapps.courseware.utils import ( _use_new_financial_assistance_flow, create_financial_assistance_application, - is_eligible_for_financial_aid + is_eligible_for_financial_aid, unpack_jwt ) from lms.djangoapps.edxnotes.helpers import is_feature_enabled from lms.djangoapps.experiments.utils import get_experiment_user_metadata_context @@ -1535,7 +1534,7 @@ def _check_sequence_exam_access(request, location): try: # unpack will validate both expiration and the requesting user matches the # token user - exam_access_unpacked = unpack_token_for(exam_access_token, request.user.id) + exam_access_unpacked = unpack_jwt(exam_access_token, request.user.id) except: # pylint: disable=bare-except log.exception(f"Failed to validate exam access token. user_id={request.user.id} location={location}") return False diff --git a/lms/envs/common.py b/lms/envs/common.py index cb7643c3668e..e17b88fe914d 100644 --- a/lms/envs/common.py +++ b/lms/envs/common.py @@ -4315,7 +4315,22 @@ def _make_locale_paths(settings): # pylint: disable=missing-function-docstring 'JWT_ISSUER': 'http://127.0.0.1:8740', 'JWT_SIGNING_ALGORITHM': 'RS512', 'JWT_SUPPORTED_VERSION': '1.2.0', - 'JWT_PUBLIC_SIGNING_JWK_SET': None, + 'JWT_PUBLIC_SIGNING_JWK_SET': '''{ + "keys": [ + { + "kid":"token-test-wrong-key", + "e": "AQAB", + "kty": "RSA", + "n": "o5cn3ljSRi6FaDEKTn0PS-oL9EFyv1pI7dffgRQLD1qf5D6sprmYfWVokSsrWig8u2y0HChSygR6Jn5KXBqQn6FpM0dDJLnWQDRXHLl3Ey1iPYgDSmOIsIGrV9ZyNCQwk03wAgWbfdBTig3QSDYD-sTNOs3pc4UD_PqAvU2nz_1SS2ZiOwOn5F6gulE1L0iE3KEUEvOIagfHNVhz0oxa_VRZILkzV-zr6R_TW1m97h4H8jXl_VJyQGyhMGGypuDrQ9_vaY_RLEulLCyY0INglHWQ7pckxBtI5q55-Vio2wgewe2_qYcGsnBGaDNbySAsvYcWRrqDiFyzrJYivodqTQ" + }, + { + "kid":"token-test-sign", + "e": "AQAB", + "kty": "RSA", + "n": "o5cn3ljSRi6FaDEKTn0PS-oL9EFyv1pI7dRgffQLD1qf5D6sprmYfWWokSsrWig8u2y0HChSygR6Jn5KXBqQn6FpM0dDJLnWQDRXHLl3Ey1iPYgDSmOIsIGrV9ZyNCQwk03wAgWbfdBTig3QSDYD-sTNOs3pc4UD_PqAvU2nz_1SS2ZiOwOn5F6gulE1L0iE3KEUEvOIagfHNVhz0oxa_VRZILkzV-zr6R_TW1m97h4H8jXl_VJyQGyhMGGypuDrQ9_vaY_RLEulLCyY0INglHWQ7pckxBtI5q55-Vio2wgewe2_qYcGsnBGaDNbySAsvYcWRrqDiFyzrJYivodqTQ" + } + ] + }''', } COURSE_CATALOG_URL_ROOT = 'http://localhost:8008' From 3bcbcaac2d5a7182ad347d541690dfd7f5bb9413 Mon Sep 17 00:00:00 2001 From: Marcos Date: Tue, 7 Jan 2025 10:46:54 -0300 Subject: [PATCH 2/9] chore: Removed unused dependency --- .../workflows/check_python_dependencies.yml | 11 +++---- lms/djangoapps/courseware/tests/test_jwt.py | 12 +++++-- lms/djangoapps/courseware/utils.py | 33 ------------------- lms/djangoapps/courseware/views/views.py | 3 +- requirements/edx/base.txt | 6 ++-- requirements/edx/development.txt | 6 ++-- requirements/edx/doc.txt | 6 ++-- requirements/edx/kernel.in | 1 - requirements/edx/semgrep.txt | 2 +- requirements/edx/testing.txt | 6 ++-- scripts/xblock/requirements.txt | 6 ++-- 11 files changed, 27 insertions(+), 65 deletions(-) diff --git a/.github/workflows/check_python_dependencies.yml b/.github/workflows/check_python_dependencies.yml index 85a4e796ce78..b691e68d4be9 100644 --- a/.github/workflows/check_python_dependencies.yml +++ b/.github/workflows/check_python_dependencies.yml @@ -14,18 +14,18 @@ jobs: steps: - name: Checkout Repository uses: actions/checkout@v4 - + - name: Set up Python uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} - + - name: Install repo-tools run: pip install edx-repo-tools[find_dependencies] - name: Install setuptool - run: pip install setuptools - + run: pip install setuptools + - name: Run Python script run: | find_python_dependencies \ @@ -35,6 +35,5 @@ jobs: --ignore https://github.com/edx/braze-client \ --ignore https://github.com/edx/edx-name-affirmation \ --ignore https://github.com/mitodl/edx-sga \ - --ignore https://github.com/edx/token-utils \ --ignore https://github.com/open-craft/xblock-poll - + diff --git a/lms/djangoapps/courseware/tests/test_jwt.py b/lms/djangoapps/courseware/tests/test_jwt.py index d21ac1778fb9..a513b1fec634 100644 --- a/lms/djangoapps/courseware/tests/test_jwt.py +++ b/lms/djangoapps/courseware/tests/test_jwt.py @@ -9,9 +9,6 @@ from lms.djangoapps.courseware.jwt import _encode_and_sign, create_jwt, unpack_jwt -import unittest -from unittest.mock import patch - test_user_id = 121 invalid_test_user_id = 120 @@ -28,6 +25,10 @@ class TestSign(unittest.TestCase): + """ + Tests for JWT creation and signing. + """ + def test_create_jwt(self): token = create_jwt(test_user_id, test_timeout, {}, test_now) @@ -53,6 +54,7 @@ def test_malformed_token(self): with self.assertRaises(BadSignature): _verify_jwt(token) + def _verify_jwt(jwt_token): """ Helper function which verifies the signature and decodes the token @@ -65,6 +67,10 @@ def _verify_jwt(jwt_token): class TestUnpack(unittest.TestCase): + """ + Tests for JWT unpacking. + """ + def test_unpack_jwt(self): token = create_jwt(test_user_id, test_timeout, {}, test_now) decoded = unpack_jwt(token, test_user_id, test_now) diff --git a/lms/djangoapps/courseware/utils.py b/lms/djangoapps/courseware/utils.py index 413e024d281b..6755c5e7b5d8 100644 --- a/lms/djangoapps/courseware/utils.py +++ b/lms/djangoapps/courseware/utils.py @@ -234,36 +234,3 @@ def _use_new_financial_assistance_flow(course_id): ): return True return False - - - -def unpack_jwt(token, lms_user_id, now=None): - """ - Unpack and verify an encoded JWT. - - Validate the user and expiration. - - Arguments: - token (string): The token to be unpacked and verified. - lms_user_id (int): LMS user ID this token should match with. - now (int): Optional now value for testing. - - Returns a valid, decoded json payload (string). - """ - now = now or int(time()) - - # Unpack and verify token - keys = jwk.KEYS() - keys.load_jwks(settings.TOKEN_SIGNING['JWT_PUBLIC_SIGNING_JWK_SET']) - payload = JWS().verify_compact(token.encode('utf-8'), keys) - - if "lms_user_id" not in payload: - raise MissingKey("LMS user id is missing") - if "exp" not in payload: - raise MissingKey("Expiration is missing") - if payload["lms_user_id"] != lms_user_id: - raise Invalid("User does not match") - if payload["exp"] < now: - raise Expired("Token is expired") - - return payload diff --git a/lms/djangoapps/courseware/views/views.py b/lms/djangoapps/courseware/views/views.py index 33bd4013f3c7..65c417279875 100644 --- a/lms/djangoapps/courseware/views/views.py +++ b/lms/djangoapps/courseware/views/views.py @@ -91,6 +91,7 @@ ) from lms.djangoapps.courseware.date_summary import verified_upgrade_deadline_link from lms.djangoapps.courseware.exceptions import CourseAccessRedirect, Redirect +from lms.djangoapps.courseware.jwt import unpack_jwt from lms.djangoapps.courseware.masquerade import is_masquerading_as_specific_student, setup_masquerade from lms.djangoapps.courseware.model_data import FieldDataCache from lms.djangoapps.courseware.models import BaseStudentModuleHistory, StudentModule @@ -105,7 +106,7 @@ from lms.djangoapps.courseware.utils import ( _use_new_financial_assistance_flow, create_financial_assistance_application, - is_eligible_for_financial_aid, unpack_jwt + is_eligible_for_financial_aid ) from lms.djangoapps.edxnotes.helpers import is_feature_enabled from lms.djangoapps.experiments.utils import get_experiment_user_metadata_context diff --git a/requirements/edx/base.txt b/requirements/edx/base.txt index 4861b7be1f63..4dc66c138a50 100644 --- a/requirements/edx/base.txt +++ b/requirements/edx/base.txt @@ -221,7 +221,6 @@ django==4.2.18 # edx-search # edx-submissions # edx-toggles - # edx-token-utils # edx-when # edxval # enmerkar @@ -546,7 +545,7 @@ edx-when==2.5.1 # edx-proctoring edxval==2.9.0 # via -r requirements/edx/kernel.in -elasticsearch==7.9.1 +elasticsearch==7.13.4 # via # -c requirements/edx/../common_constraints.txt # -c requirements/edx/../constraints.txt @@ -931,7 +930,6 @@ pygments==2.19.1 pyjwkest==1.4.2 # via # -r requirements/edx/kernel.in - # edx-token-utils # lti-consumer-xblock pyjwt[crypto]==2.10.1 # via @@ -1215,7 +1213,7 @@ uritemplate==4.1.1 # drf-spectacular # drf-yasg # google-api-python-client -urllib3==2.2.3 +urllib3==1.26.20 # via # -c requirements/edx/../common_constraints.txt # botocore diff --git a/requirements/edx/development.txt b/requirements/edx/development.txt index ae377bd5cc7c..84e2a26239b3 100644 --- a/requirements/edx/development.txt +++ b/requirements/edx/development.txt @@ -395,7 +395,6 @@ django==4.2.18 # edx-search # edx-submissions # edx-toggles - # edx-token-utils # edx-when # edxval # enmerkar @@ -858,7 +857,7 @@ edxval==2.9.0 # via # -r requirements/edx/doc.txt # -r requirements/edx/testing.txt -elasticsearch==7.9.1 +elasticsearch==7.13.4 # via # -c requirements/edx/../common_constraints.txt # -c requirements/edx/../constraints.txt @@ -1598,7 +1597,6 @@ pyjwkest==1.4.2 # via # -r requirements/edx/doc.txt # -r requirements/edx/testing.txt - # edx-token-utils # lti-consumer-xblock pyjwt[crypto]==2.10.1 # via @@ -2172,7 +2170,7 @@ uritemplate==4.1.1 # drf-spectacular # drf-yasg # google-api-python-client -urllib3==2.2.3 +urllib3==1.26.20 # via # -c requirements/edx/../common_constraints.txt # -r requirements/edx/doc.txt diff --git a/requirements/edx/doc.txt b/requirements/edx/doc.txt index 354e85c98f55..2b7072e447f0 100644 --- a/requirements/edx/doc.txt +++ b/requirements/edx/doc.txt @@ -280,7 +280,6 @@ django==4.2.18 # edx-search # edx-submissions # edx-toggles - # edx-token-utils # edx-when # edxval # enmerkar @@ -637,7 +636,7 @@ edx-when==2.5.1 # edx-proctoring edxval==2.9.0 # via -r requirements/edx/base.txt -elasticsearch==7.9.1 +elasticsearch==7.13.4 # via # -c requirements/edx/../common_constraints.txt # -c requirements/edx/../constraints.txt @@ -1147,7 +1146,6 @@ pygments==2.19.1 pyjwkest==1.4.2 # via # -r requirements/edx/base.txt - # edx-token-utils # lti-consumer-xblock pyjwt[crypto]==2.10.1 # via @@ -1529,7 +1527,7 @@ uritemplate==4.1.1 # drf-spectacular # drf-yasg # google-api-python-client -urllib3==2.2.3 +urllib3==1.26.20 # via # -c requirements/edx/../common_constraints.txt # -r requirements/edx/base.txt diff --git a/requirements/edx/kernel.in b/requirements/edx/kernel.in index d1a132778133..a17b9db4c868 100644 --- a/requirements/edx/kernel.in +++ b/requirements/edx/kernel.in @@ -84,7 +84,6 @@ edx-rest-api-client edx-search edx-submissions edx-toggles # Feature toggles management -edx-token-utils # Validate exam access tokens edx-when edxval event-tracking diff --git a/requirements/edx/semgrep.txt b/requirements/edx/semgrep.txt index 65a0b794b9e6..0caf869df716 100644 --- a/requirements/edx/semgrep.txt +++ b/requirements/edx/semgrep.txt @@ -125,7 +125,7 @@ typing-extensions==4.12.2 # opentelemetry-sdk # referencing # semgrep -urllib3==2.2.3 +urllib3==2.3.0 # via # -c requirements/edx/../common_constraints.txt # requests diff --git a/requirements/edx/testing.txt b/requirements/edx/testing.txt index 799f8b16ed26..29bc1e960b16 100644 --- a/requirements/edx/testing.txt +++ b/requirements/edx/testing.txt @@ -306,7 +306,6 @@ django==4.2.18 # edx-search # edx-submissions # edx-toggles - # edx-token-utils # edx-when # edxval # enmerkar @@ -660,7 +659,7 @@ edx-when==2.5.1 # edx-proctoring edxval==2.9.0 # via -r requirements/edx/base.txt -elasticsearch==7.9.1 +elasticsearch==7.13.4 # via # -c requirements/edx/../common_constraints.txt # -c requirements/edx/../constraints.txt @@ -1211,7 +1210,6 @@ pygments==2.19.1 pyjwkest==1.4.2 # via # -r requirements/edx/base.txt - # edx-token-utils # lti-consumer-xblock pyjwt[crypto]==2.10.1 # via @@ -1615,7 +1613,7 @@ uritemplate==4.1.1 # drf-spectacular # drf-yasg # google-api-python-client -urllib3==2.2.3 +urllib3==1.26.20 # via # -c requirements/edx/../common_constraints.txt # -r requirements/edx/base.txt diff --git a/scripts/xblock/requirements.txt b/scripts/xblock/requirements.txt index 9af137853dc5..aabed41ba0a9 100644 --- a/scripts/xblock/requirements.txt +++ b/scripts/xblock/requirements.txt @@ -14,7 +14,5 @@ idna==3.10 # via requests requests==2.32.3 # via -r scripts/xblock/requirements.in -urllib3==2.2.3 - # via - # -c scripts/xblock/../../requirements/common_constraints.txt - # requests +urllib3==2.3.0 + # via requests From 3bbf8aca7f7365811a53bd9f1cf4f9aa087f22c9 Mon Sep 17 00:00:00 2001 From: Marcos Date: Tue, 7 Jan 2025 10:46:54 -0300 Subject: [PATCH 3/9] chore: Run make compile-requirements to update dependencies --- lms/djangoapps/courseware/utils.py | 5 ----- requirements/edx/base.txt | 2 +- requirements/edx/development.txt | 2 +- requirements/edx/doc.txt | 2 +- requirements/edx/semgrep.txt | 2 +- requirements/edx/testing.txt | 2 +- scripts/xblock/requirements.txt | 6 ++++-- 7 files changed, 9 insertions(+), 12 deletions(-) diff --git a/lms/djangoapps/courseware/utils.py b/lms/djangoapps/courseware/utils.py index 6755c5e7b5d8..5409c89f636b 100644 --- a/lms/djangoapps/courseware/utils.py +++ b/lms/djangoapps/courseware/utils.py @@ -5,8 +5,6 @@ import hashlib import logging -from time import time - from django.conf import settings from django.http import HttpResponse, HttpResponseBadRequest from edx_rest_api_client.client import OAuthAPIClient @@ -17,9 +15,6 @@ ENROLLMENT_TRACK_PARTITION_ID # lint-amnesty, pylint: disable=wrong-import-order from xmodule.partitions.partitions_service import PartitionService # lint-amnesty, pylint: disable=wrong-import-order -from jwkest import Expired, Invalid, MissingKey, jwk -from jwkest.jws import JWS - from common.djangoapps.course_modes.models import CourseMode from lms.djangoapps.commerce.utils import EcommerceService from lms.djangoapps.courseware.config import ENABLE_NEW_FINANCIAL_ASSISTANCE_FLOW diff --git a/requirements/edx/base.txt b/requirements/edx/base.txt index 4dc66c138a50..b9179d11ffab 100644 --- a/requirements/edx/base.txt +++ b/requirements/edx/base.txt @@ -545,7 +545,7 @@ edx-when==2.5.1 # edx-proctoring edxval==2.9.0 # via -r requirements/edx/kernel.in -elasticsearch==7.13.4 +elasticsearch==7.9.1 # via # -c requirements/edx/../common_constraints.txt # -c requirements/edx/../constraints.txt diff --git a/requirements/edx/development.txt b/requirements/edx/development.txt index 84e2a26239b3..8ec4d878f0d4 100644 --- a/requirements/edx/development.txt +++ b/requirements/edx/development.txt @@ -857,7 +857,7 @@ edxval==2.9.0 # via # -r requirements/edx/doc.txt # -r requirements/edx/testing.txt -elasticsearch==7.13.4 +elasticsearch==7.9.1 # via # -c requirements/edx/../common_constraints.txt # -c requirements/edx/../constraints.txt diff --git a/requirements/edx/doc.txt b/requirements/edx/doc.txt index 2b7072e447f0..1619e3972557 100644 --- a/requirements/edx/doc.txt +++ b/requirements/edx/doc.txt @@ -636,7 +636,7 @@ edx-when==2.5.1 # edx-proctoring edxval==2.9.0 # via -r requirements/edx/base.txt -elasticsearch==7.13.4 +elasticsearch==7.9.1 # via # -c requirements/edx/../common_constraints.txt # -c requirements/edx/../constraints.txt diff --git a/requirements/edx/semgrep.txt b/requirements/edx/semgrep.txt index 0caf869df716..65a0b794b9e6 100644 --- a/requirements/edx/semgrep.txt +++ b/requirements/edx/semgrep.txt @@ -125,7 +125,7 @@ typing-extensions==4.12.2 # opentelemetry-sdk # referencing # semgrep -urllib3==2.3.0 +urllib3==2.2.3 # via # -c requirements/edx/../common_constraints.txt # requests diff --git a/requirements/edx/testing.txt b/requirements/edx/testing.txt index 29bc1e960b16..8f007851e635 100644 --- a/requirements/edx/testing.txt +++ b/requirements/edx/testing.txt @@ -659,7 +659,7 @@ edx-when==2.5.1 # edx-proctoring edxval==2.9.0 # via -r requirements/edx/base.txt -elasticsearch==7.13.4 +elasticsearch==7.9.1 # via # -c requirements/edx/../common_constraints.txt # -c requirements/edx/../constraints.txt diff --git a/scripts/xblock/requirements.txt b/scripts/xblock/requirements.txt index aabed41ba0a9..9af137853dc5 100644 --- a/scripts/xblock/requirements.txt +++ b/scripts/xblock/requirements.txt @@ -14,5 +14,7 @@ idna==3.10 # via requests requests==2.32.3 # via -r scripts/xblock/requirements.in -urllib3==2.3.0 - # via requests +urllib3==2.2.3 + # via + # -c scripts/xblock/../../requirements/common_constraints.txt + # requests From ca3cd45a7954bb5509c88ce6b0a8fd6883a5ce8b Mon Sep 17 00:00:00 2001 From: Marcos Date: Thu, 9 Jan 2025 09:38:53 -0300 Subject: [PATCH 4/9] chore: fix tests --- lms/djangoapps/courseware/tests/test_views.py | 8 ++--- lms/envs/common.py | 18 ++--------- lms/envs/test.py | 32 +++++++++++++++++++ 3 files changed, 38 insertions(+), 20 deletions(-) diff --git a/lms/djangoapps/courseware/tests/test_views.py b/lms/djangoapps/courseware/tests/test_views.py index 2bedcd0d8e33..da31ebccce45 100644 --- a/lms/djangoapps/courseware/tests/test_views.py +++ b/lms/djangoapps/courseware/tests/test_views.py @@ -2933,9 +2933,9 @@ def test_render_xblock_with_course_duration_limits_in_mobile_browser(self, mock_ ) @ddt.unpack @patch.dict('django.conf.settings.FEATURES', {'ENABLE_PROCTORED_EXAMS': True}) - @patch('lms.djangoapps.courseware.views.views.unpack_token_for') + @patch('lms.djangoapps.courseware.views.views.unpack_jwt') def test_render_descendant_of_exam_gated_by_access_token(self, exam_access_token, - expected_response, _mock_token_unpack): + expected_response, _mock_unpack_jwt): """ Verify blocks inside an exam that requires token access are gated by a valid exam access JWT issued for that exam sequence. @@ -2968,7 +2968,7 @@ def test_render_descendant_of_exam_gated_by_access_token(self, exam_access_token CourseOverview.load_from_module_store(self.course.id) self.setup_user(admin=False, enroll=True, login=True) - def _mock_token_unpack_fn(token, user_id): + def _mock_unpack_jwt_fn(token, user_id): if token == 'valid-jwt-for-exam-sequence': return {'content_id': str(self.sequence.location)} elif token == 'valid-jwt-for-incorrect-sequence': @@ -2976,7 +2976,7 @@ def _mock_token_unpack_fn(token, user_id): else: raise Exception('invalid JWT') - _mock_token_unpack.side_effect = _mock_token_unpack_fn + _mock_unpack_jwt.side_effect = _mock_unpack_jwt_fn # Problem and Vertical response should be gated on access token for block in [self.problem_block, self.vertical_block]: diff --git a/lms/envs/common.py b/lms/envs/common.py index e17b88fe914d..5ca5bcac9a4a 100644 --- a/lms/envs/common.py +++ b/lms/envs/common.py @@ -4315,22 +4315,8 @@ def _make_locale_paths(settings): # pylint: disable=missing-function-docstring 'JWT_ISSUER': 'http://127.0.0.1:8740', 'JWT_SIGNING_ALGORITHM': 'RS512', 'JWT_SUPPORTED_VERSION': '1.2.0', - 'JWT_PUBLIC_SIGNING_JWK_SET': '''{ - "keys": [ - { - "kid":"token-test-wrong-key", - "e": "AQAB", - "kty": "RSA", - "n": "o5cn3ljSRi6FaDEKTn0PS-oL9EFyv1pI7dffgRQLD1qf5D6sprmYfWVokSsrWig8u2y0HChSygR6Jn5KXBqQn6FpM0dDJLnWQDRXHLl3Ey1iPYgDSmOIsIGrV9ZyNCQwk03wAgWbfdBTig3QSDYD-sTNOs3pc4UD_PqAvU2nz_1SS2ZiOwOn5F6gulE1L0iE3KEUEvOIagfHNVhz0oxa_VRZILkzV-zr6R_TW1m97h4H8jXl_VJyQGyhMGGypuDrQ9_vaY_RLEulLCyY0INglHWQ7pckxBtI5q55-Vio2wgewe2_qYcGsnBGaDNbySAsvYcWRrqDiFyzrJYivodqTQ" - }, - { - "kid":"token-test-sign", - "e": "AQAB", - "kty": "RSA", - "n": "o5cn3ljSRi6FaDEKTn0PS-oL9EFyv1pI7dRgffQLD1qf5D6sprmYfWWokSsrWig8u2y0HChSygR6Jn5KXBqQn6FpM0dDJLnWQDRXHLl3Ey1iPYgDSmOIsIGrV9ZyNCQwk03wAgWbfdBTig3QSDYD-sTNOs3pc4UD_PqAvU2nz_1SS2ZiOwOn5F6gulE1L0iE3KEUEvOIagfHNVhz0oxa_VRZILkzV-zr6R_TW1m97h4H8jXl_VJyQGyhMGGypuDrQ9_vaY_RLEulLCyY0INglHWQ7pckxBtI5q55-Vio2wgewe2_qYcGsnBGaDNbySAsvYcWRrqDiFyzrJYivodqTQ" - } - ] - }''', + 'JWT_PRIVATE_SIGNING_JWK': None, + 'JWT_PUBLIC_SIGNING_JWK_SET': None, } COURSE_CATALOG_URL_ROOT = 'http://localhost:8008' diff --git a/lms/envs/test.py b/lms/envs/test.py index a9e8aaf9f2e2..38c12370f1c7 100644 --- a/lms/envs/test.py +++ b/lms/envs/test.py @@ -657,3 +657,35 @@ # case of new django version these values will override. if django.VERSION[0] >= 4: # for greater than django 3.2 use with schemes. CSRF_TRUSTED_ORIGINS = CSRF_TRUSTED_ORIGINS_WITH_SCHEME + + +############## Settings for JWT token handling ############## +TOKEN_SIGNING = { + 'JWT_ISSUER': 'token-test-issuer', + 'JWT_SIGNING_ALGORITHM': 'RS512', + 'JWT_SUPPORTED_VERSION': '1.2.0', + 'JWT_PRIVATE_SIGNING_JWK': '''{ + "e": "AQAB", + "d": "HIiV7KNjcdhVbpn3KT-I9n3JPf5YbGXsCIedmPqDH1d4QhBofuAqZ9zebQuxkRUpmqtYMv0Zi6ECSUqH387GYQF_XvFUFcjQRPycISd8TH0DAKaDpGr-AYNshnKiEtQpINhcP44I1AYNPCwyoxXA1fGTtmkKChsuWea7o8kytwU5xSejvh5-jiqu2SF4GEl0BEXIAPZsgbzoPIWNxgO4_RzNnWs6nJZeszcaDD0CyezVSuH9QcI6g5QFzAC_YuykSsaaFJhZ05DocBsLczShJ9Omf6PnK9xlm26I84xrEh_7x4fVmNBg3xWTLh8qOnHqGko93A1diLRCrKHOvnpvgQ", + "n": "o5cn3ljSRi6FaDEKTn0PS-oL9EFyv1pI7dRgffQLD1qf5D6sprmYfWWokSsrWig8u2y0HChSygR6Jn5KXBqQn6FpM0dDJLnWQDRXHLl3Ey1iPYgDSmOIsIGrV9ZyNCQwk03wAgWbfdBTig3QSDYD-sTNOs3pc4UD_PqAvU2nz_1SS2ZiOwOn5F6gulE1L0iE3KEUEvOIagfHNVhz0oxa_VRZILkzV-zr6R_TW1m97h4H8jXl_VJyQGyhMGGypuDrQ9_vaY_RLEulLCyY0INglHWQ7pckxBtI5q55-Vio2wgewe2_qYcGsnBGaDNbySAsvYcWRrqDiFyzrJYivodqTQ", + "q": "3T3DEtBUka7hLGdIsDlC96Uadx_q_E4Vb1cxx_4Ss_wGp1Loz3N3ZngGyInsKlmbBgLo1Ykd6T9TRvRNEWEtFSOcm2INIBoVoXk7W5RuPa8Cgq2tjQj9ziGQ08JMejrPlj3Q1wmALJr5VTfvSYBu0WkljhKNCy1KB6fCby0C9WE", + "p": "vUqzWPZnDG4IXyo-k5F0bHV0BNL_pVhQoLW7eyFHnw74IOEfSbdsMspNcPSFIrtgPsn7981qv3lN_staZ6JflKfHayjB_lvltHyZxfl0dvruShZOx1N6ykEo7YrAskC_qxUyrIvqmJ64zPW3jkuOYrFs7Ykj3zFx3Zq1H5568G0", + "kid": "token-test-sign", "kty": "RSA" + }''', + 'JWT_PUBLIC_SIGNING_JWK_SET': '''{ + "keys": [ + { + "kid":"token-test-wrong-key", + "e": "AQAB", + "kty": "RSA", + "n": "o5cn3ljSRi6FaDEKTn0PS-oL9EFyv1pI7dffgRQLD1qf5D6sprmYfWVokSsrWig8u2y0HChSygR6Jn5KXBqQn6FpM0dDJLnWQDRXHLl3Ey1iPYgDSmOIsIGrV9ZyNCQwk03wAgWbfdBTig3QSDYD-sTNOs3pc4UD_PqAvU2nz_1SS2ZiOwOn5F6gulE1L0iE3KEUEvOIagfHNVhz0oxa_VRZILkzV-zr6R_TW1m97h4H8jXl_VJyQGyhMGGypuDrQ9_vaY_RLEulLCyY0INglHWQ7pckxBtI5q55-Vio2wgewe2_qYcGsnBGaDNbySAsvYcWRrqDiFyzrJYivodqTQ" + }, + { + "kid":"token-test-sign", + "e": "AQAB", + "kty": "RSA", + "n": "o5cn3ljSRi6FaDEKTn0PS-oL9EFyv1pI7dRgffQLD1qf5D6sprmYfWWokSsrWig8u2y0HChSygR6Jn5KXBqQn6FpM0dDJLnWQDRXHLl3Ey1iPYgDSmOIsIGrV9ZyNCQwk03wAgWbfdBTig3QSDYD-sTNOs3pc4UD_PqAvU2nz_1SS2ZiOwOn5F6gulE1L0iE3KEUEvOIagfHNVhz0oxa_VRZILkzV-zr6R_TW1m97h4H8jXl_VJyQGyhMGGypuDrQ9_vaY_RLEulLCyY0INglHWQ7pckxBtI5q55-Vio2wgewe2_qYcGsnBGaDNbySAsvYcWRrqDiFyzrJYivodqTQ" + } + ] + }''', +} From 67a56c270fa51cce4fec7ca9f3b1f9d5059686b4 Mon Sep 17 00:00:00 2001 From: Marcos Date: Wed, 15 Jan 2025 15:04:38 -0300 Subject: [PATCH 5/9] chore: Moved jwt file to openedx.core.lib --- lms/djangoapps/courseware/views/views.py | 2 +- {lms/djangoapps/courseware => openedx/core/lib}/jwt.py | 0 .../courseware => openedx/core/lib}/tests/test_jwt.py | 2 +- requirements/edx/base.txt | 5 +---- requirements/edx/development.txt | 2 +- requirements/edx/doc.txt | 2 +- requirements/edx/testing.txt | 2 +- 7 files changed, 6 insertions(+), 9 deletions(-) rename {lms/djangoapps/courseware => openedx/core/lib}/jwt.py (100%) rename {lms/djangoapps/courseware => openedx/core/lib}/tests/test_jwt.py (97%) diff --git a/lms/djangoapps/courseware/views/views.py b/lms/djangoapps/courseware/views/views.py index 65c417279875..19eabe692997 100644 --- a/lms/djangoapps/courseware/views/views.py +++ b/lms/djangoapps/courseware/views/views.py @@ -91,7 +91,6 @@ ) from lms.djangoapps.courseware.date_summary import verified_upgrade_deadline_link from lms.djangoapps.courseware.exceptions import CourseAccessRedirect, Redirect -from lms.djangoapps.courseware.jwt import unpack_jwt from lms.djangoapps.courseware.masquerade import is_masquerading_as_specific_student, setup_masquerade from lms.djangoapps.courseware.model_data import FieldDataCache from lms.djangoapps.courseware.models import BaseStudentModuleHistory, StudentModule @@ -138,6 +137,7 @@ from openedx.core.djangoapps.zendesk_proxy.utils import create_zendesk_ticket from openedx.core.djangolib.markup import HTML, Text from openedx.core.lib.courses import get_course_by_id +from openedx.core.lib.jwt import unpack_jwt from openedx.core.lib.mobile_utils import is_request_from_mobile_app from openedx.features.course_duration_limits.access import generate_course_expired_fragment from openedx.features.course_experience import course_home_url diff --git a/lms/djangoapps/courseware/jwt.py b/openedx/core/lib/jwt.py similarity index 100% rename from lms/djangoapps/courseware/jwt.py rename to openedx/core/lib/jwt.py diff --git a/lms/djangoapps/courseware/tests/test_jwt.py b/openedx/core/lib/tests/test_jwt.py similarity index 97% rename from lms/djangoapps/courseware/tests/test_jwt.py rename to openedx/core/lib/tests/test_jwt.py index a513b1fec634..79caf0207fa1 100644 --- a/lms/djangoapps/courseware/tests/test_jwt.py +++ b/openedx/core/lib/tests/test_jwt.py @@ -7,7 +7,7 @@ from jwkest import BadSignature, Expired, Invalid, MissingKey, jwk from jwkest.jws import JWS -from lms.djangoapps.courseware.jwt import _encode_and_sign, create_jwt, unpack_jwt +from openedx.core.lib.jwt import _encode_and_sign, create_jwt, unpack_jwt test_user_id = 121 diff --git a/requirements/edx/base.txt b/requirements/edx/base.txt index b9179d11ffab..c8f2af138c64 100644 --- a/requirements/edx/base.txt +++ b/requirements/edx/base.txt @@ -80,9 +80,6 @@ boto3==1.36.3 # ora2 botocore==1.36.3 # via - # -r requirements/edx/kernel.in - # boto3 - # s3transfer bridgekeeper==0.9 # via -r requirements/edx/kernel.in cachecontrol==0.14.2 @@ -1213,7 +1210,7 @@ uritemplate==4.1.1 # drf-spectacular # drf-yasg # google-api-python-client -urllib3==1.26.20 +urllib3==2.2.3 # via # -c requirements/edx/../common_constraints.txt # botocore diff --git a/requirements/edx/development.txt b/requirements/edx/development.txt index 8ec4d878f0d4..61305f1cbc5b 100644 --- a/requirements/edx/development.txt +++ b/requirements/edx/development.txt @@ -2170,7 +2170,7 @@ uritemplate==4.1.1 # drf-spectacular # drf-yasg # google-api-python-client -urllib3==1.26.20 +urllib3==2.2.3 # via # -c requirements/edx/../common_constraints.txt # -r requirements/edx/doc.txt diff --git a/requirements/edx/doc.txt b/requirements/edx/doc.txt index 1619e3972557..c08b5b6fb396 100644 --- a/requirements/edx/doc.txt +++ b/requirements/edx/doc.txt @@ -1527,7 +1527,7 @@ uritemplate==4.1.1 # drf-spectacular # drf-yasg # google-api-python-client -urllib3==1.26.20 +urllib3==2.2.3 # via # -c requirements/edx/../common_constraints.txt # -r requirements/edx/base.txt diff --git a/requirements/edx/testing.txt b/requirements/edx/testing.txt index 8f007851e635..ae5aed4234f4 100644 --- a/requirements/edx/testing.txt +++ b/requirements/edx/testing.txt @@ -1613,7 +1613,7 @@ uritemplate==4.1.1 # drf-spectacular # drf-yasg # google-api-python-client -urllib3==1.26.20 +urllib3==2.2.3 # via # -c requirements/edx/../common_constraints.txt # -r requirements/edx/base.txt From 6e9888974836b1622f8546cdc5f00594c247a950 Mon Sep 17 00:00:00 2001 From: Marcos Date: Wed, 15 Jan 2025 15:50:50 -0300 Subject: [PATCH 6/9] chore: Updated TOKEN_SIGNING on cms --- cms/envs/test.py | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/cms/envs/test.py b/cms/envs/test.py index 49db50608858..d391ccba5e98 100644 --- a/cms/envs/test.py +++ b/cms/envs/test.py @@ -343,3 +343,34 @@ } } } + +############## Settings for JWT token handling ############## +TOKEN_SIGNING = { + 'JWT_ISSUER': 'token-test-issuer', + 'JWT_SIGNING_ALGORITHM': 'RS512', + 'JWT_SUPPORTED_VERSION': '1.2.0', + 'JWT_PRIVATE_SIGNING_JWK': '''{ + "e": "AQAB", + "d": "HIiV7KNjcdhVbpn3KT-I9n3JPf5YbGXsCIedmPqDH1d4QhBofuAqZ9zebQuxkRUpmqtYMv0Zi6ECSUqH387GYQF_XvFUFcjQRPycISd8TH0DAKaDpGr-AYNshnKiEtQpINhcP44I1AYNPCwyoxXA1fGTtmkKChsuWea7o8kytwU5xSejvh5-jiqu2SF4GEl0BEXIAPZsgbzoPIWNxgO4_RzNnWs6nJZeszcaDD0CyezVSuH9QcI6g5QFzAC_YuykSsaaFJhZ05DocBsLczShJ9Omf6PnK9xlm26I84xrEh_7x4fVmNBg3xWTLh8qOnHqGko93A1diLRCrKHOvnpvgQ", + "n": "o5cn3ljSRi6FaDEKTn0PS-oL9EFyv1pI7dRgffQLD1qf5D6sprmYfWWokSsrWig8u2y0HChSygR6Jn5KXBqQn6FpM0dDJLnWQDRXHLl3Ey1iPYgDSmOIsIGrV9ZyNCQwk03wAgWbfdBTig3QSDYD-sTNOs3pc4UD_PqAvU2nz_1SS2ZiOwOn5F6gulE1L0iE3KEUEvOIagfHNVhz0oxa_VRZILkzV-zr6R_TW1m97h4H8jXl_VJyQGyhMGGypuDrQ9_vaY_RLEulLCyY0INglHWQ7pckxBtI5q55-Vio2wgewe2_qYcGsnBGaDNbySAsvYcWRrqDiFyzrJYivodqTQ", + "q": "3T3DEtBUka7hLGdIsDlC96Uadx_q_E4Vb1cxx_4Ss_wGp1Loz3N3ZngGyInsKlmbBgLo1Ykd6T9TRvRNEWEtFSOcm2INIBoVoXk7W5RuPa8Cgq2tjQj9ziGQ08JMejrPlj3Q1wmALJr5VTfvSYBu0WkljhKNCy1KB6fCby0C9WE", + "p": "vUqzWPZnDG4IXyo-k5F0bHV0BNL_pVhQoLW7eyFHnw74IOEfSbdsMspNcPSFIrtgPsn7981qv3lN_staZ6JflKfHayjB_lvltHyZxfl0dvruShZOx1N6ykEo7YrAskC_qxUyrIvqmJ64zPW3jkuOYrFs7Ykj3zFx3Zq1H5568G0", + "kid": "token-test-sign", "kty": "RSA" + }''', + 'JWT_PUBLIC_SIGNING_JWK_SET': '''{ + "keys": [ + { + "kid":"token-test-wrong-key", + "e": "AQAB", + "kty": "RSA", + "n": "o5cn3ljSRi6FaDEKTn0PS-oL9EFyv1pI7dffgRQLD1qf5D6sprmYfWVokSsrWig8u2y0HChSygR6Jn5KXBqQn6FpM0dDJLnWQDRXHLl3Ey1iPYgDSmOIsIGrV9ZyNCQwk03wAgWbfdBTig3QSDYD-sTNOs3pc4UD_PqAvU2nz_1SS2ZiOwOn5F6gulE1L0iE3KEUEvOIagfHNVhz0oxa_VRZILkzV-zr6R_TW1m97h4H8jXl_VJyQGyhMGGypuDrQ9_vaY_RLEulLCyY0INglHWQ7pckxBtI5q55-Vio2wgewe2_qYcGsnBGaDNbySAsvYcWRrqDiFyzrJYivodqTQ" + }, + { + "kid":"token-test-sign", + "e": "AQAB", + "kty": "RSA", + "n": "o5cn3ljSRi6FaDEKTn0PS-oL9EFyv1pI7dRgffQLD1qf5D6sprmYfWWokSsrWig8u2y0HChSygR6Jn5KXBqQn6FpM0dDJLnWQDRXHLl3Ey1iPYgDSmOIsIGrV9ZyNCQwk03wAgWbfdBTig3QSDYD-sTNOs3pc4UD_PqAvU2nz_1SS2ZiOwOn5F6gulE1L0iE3KEUEvOIagfHNVhz0oxa_VRZILkzV-zr6R_TW1m97h4H8jXl_VJyQGyhMGGypuDrQ9_vaY_RLEulLCyY0INglHWQ7pckxBtI5q55-Vio2wgewe2_qYcGsnBGaDNbySAsvYcWRrqDiFyzrJYivodqTQ" + } + ] + }''', +} From 5d494feb0197f08827b13ca49e272e7c6b4eab82 Mon Sep 17 00:00:00 2001 From: Marcos Date: Wed, 15 Jan 2025 16:10:04 -0300 Subject: [PATCH 7/9] chore: Updated defaults for token handling on CMS --- cms/envs/common.py | 9 +++++++++ lms/envs/common.py | 1 + 2 files changed, 10 insertions(+) diff --git a/cms/envs/common.py b/cms/envs/common.py index 591247388a9d..8f068a5c0072 100644 --- a/cms/envs/common.py +++ b/cms/envs/common.py @@ -2530,6 +2530,15 @@ EXAMS_SERVICE_URL = 'http://localhost:18740/api/v1' EXAMS_SERVICE_USERNAME = 'edx_exams_worker' +############## Settings for JWT token handling ############## +TOKEN_SIGNING = { + 'JWT_ISSUER': 'http://127.0.0.1:8740', + 'JWT_SIGNING_ALGORITHM': 'RS512', + 'JWT_SUPPORTED_VERSION': '1.2.0', + 'JWT_PRIVATE_SIGNING_JWK': None, + 'JWT_PUBLIC_SIGNING_JWK_SET': None, +} + FINANCIAL_REPORTS = { 'STORAGE_TYPE': 'localfs', 'BUCKET': None, diff --git a/lms/envs/common.py b/lms/envs/common.py index 5ca5bcac9a4a..8c966e67f6d0 100644 --- a/lms/envs/common.py +++ b/lms/envs/common.py @@ -4311,6 +4311,7 @@ def _make_locale_paths(settings): # pylint: disable=missing-function-docstring # Exam Service EXAMS_SERVICE_URL = 'http://localhost:18740/api/v1' +############## Settings for JWT token handling ############## TOKEN_SIGNING = { 'JWT_ISSUER': 'http://127.0.0.1:8740', 'JWT_SIGNING_ALGORITHM': 'RS512', From 747289bc1a748573889be4b7f777ecc19bfd7b07 Mon Sep 17 00:00:00 2001 From: Marcos Date: Thu, 23 Jan 2025 12:41:37 -0300 Subject: [PATCH 8/9] fix: Removed JWT constants from CMS and added comments on how to generate them --- cms/envs/common.py | 9 --------- cms/envs/test.py | 31 ------------------------------ lms/envs/common.py | 6 ++++++ openedx/core/lib/tests/test_jwt.py | 3 +++ requirements/edx/base.txt | 5 +++-- requirements/edx/development.txt | 4 ---- requirements/edx/doc.txt | 2 -- requirements/edx/testing.txt | 2 -- 8 files changed, 12 insertions(+), 50 deletions(-) diff --git a/cms/envs/common.py b/cms/envs/common.py index 8f068a5c0072..591247388a9d 100644 --- a/cms/envs/common.py +++ b/cms/envs/common.py @@ -2530,15 +2530,6 @@ EXAMS_SERVICE_URL = 'http://localhost:18740/api/v1' EXAMS_SERVICE_USERNAME = 'edx_exams_worker' -############## Settings for JWT token handling ############## -TOKEN_SIGNING = { - 'JWT_ISSUER': 'http://127.0.0.1:8740', - 'JWT_SIGNING_ALGORITHM': 'RS512', - 'JWT_SUPPORTED_VERSION': '1.2.0', - 'JWT_PRIVATE_SIGNING_JWK': None, - 'JWT_PUBLIC_SIGNING_JWK_SET': None, -} - FINANCIAL_REPORTS = { 'STORAGE_TYPE': 'localfs', 'BUCKET': None, diff --git a/cms/envs/test.py b/cms/envs/test.py index d391ccba5e98..49db50608858 100644 --- a/cms/envs/test.py +++ b/cms/envs/test.py @@ -343,34 +343,3 @@ } } } - -############## Settings for JWT token handling ############## -TOKEN_SIGNING = { - 'JWT_ISSUER': 'token-test-issuer', - 'JWT_SIGNING_ALGORITHM': 'RS512', - 'JWT_SUPPORTED_VERSION': '1.2.0', - 'JWT_PRIVATE_SIGNING_JWK': '''{ - "e": "AQAB", - "d": "HIiV7KNjcdhVbpn3KT-I9n3JPf5YbGXsCIedmPqDH1d4QhBofuAqZ9zebQuxkRUpmqtYMv0Zi6ECSUqH387GYQF_XvFUFcjQRPycISd8TH0DAKaDpGr-AYNshnKiEtQpINhcP44I1AYNPCwyoxXA1fGTtmkKChsuWea7o8kytwU5xSejvh5-jiqu2SF4GEl0BEXIAPZsgbzoPIWNxgO4_RzNnWs6nJZeszcaDD0CyezVSuH9QcI6g5QFzAC_YuykSsaaFJhZ05DocBsLczShJ9Omf6PnK9xlm26I84xrEh_7x4fVmNBg3xWTLh8qOnHqGko93A1diLRCrKHOvnpvgQ", - "n": "o5cn3ljSRi6FaDEKTn0PS-oL9EFyv1pI7dRgffQLD1qf5D6sprmYfWWokSsrWig8u2y0HChSygR6Jn5KXBqQn6FpM0dDJLnWQDRXHLl3Ey1iPYgDSmOIsIGrV9ZyNCQwk03wAgWbfdBTig3QSDYD-sTNOs3pc4UD_PqAvU2nz_1SS2ZiOwOn5F6gulE1L0iE3KEUEvOIagfHNVhz0oxa_VRZILkzV-zr6R_TW1m97h4H8jXl_VJyQGyhMGGypuDrQ9_vaY_RLEulLCyY0INglHWQ7pckxBtI5q55-Vio2wgewe2_qYcGsnBGaDNbySAsvYcWRrqDiFyzrJYivodqTQ", - "q": "3T3DEtBUka7hLGdIsDlC96Uadx_q_E4Vb1cxx_4Ss_wGp1Loz3N3ZngGyInsKlmbBgLo1Ykd6T9TRvRNEWEtFSOcm2INIBoVoXk7W5RuPa8Cgq2tjQj9ziGQ08JMejrPlj3Q1wmALJr5VTfvSYBu0WkljhKNCy1KB6fCby0C9WE", - "p": "vUqzWPZnDG4IXyo-k5F0bHV0BNL_pVhQoLW7eyFHnw74IOEfSbdsMspNcPSFIrtgPsn7981qv3lN_staZ6JflKfHayjB_lvltHyZxfl0dvruShZOx1N6ykEo7YrAskC_qxUyrIvqmJ64zPW3jkuOYrFs7Ykj3zFx3Zq1H5568G0", - "kid": "token-test-sign", "kty": "RSA" - }''', - 'JWT_PUBLIC_SIGNING_JWK_SET': '''{ - "keys": [ - { - "kid":"token-test-wrong-key", - "e": "AQAB", - "kty": "RSA", - "n": "o5cn3ljSRi6FaDEKTn0PS-oL9EFyv1pI7dffgRQLD1qf5D6sprmYfWVokSsrWig8u2y0HChSygR6Jn5KXBqQn6FpM0dDJLnWQDRXHLl3Ey1iPYgDSmOIsIGrV9ZyNCQwk03wAgWbfdBTig3QSDYD-sTNOs3pc4UD_PqAvU2nz_1SS2ZiOwOn5F6gulE1L0iE3KEUEvOIagfHNVhz0oxa_VRZILkzV-zr6R_TW1m97h4H8jXl_VJyQGyhMGGypuDrQ9_vaY_RLEulLCyY0INglHWQ7pckxBtI5q55-Vio2wgewe2_qYcGsnBGaDNbySAsvYcWRrqDiFyzrJYivodqTQ" - }, - { - "kid":"token-test-sign", - "e": "AQAB", - "kty": "RSA", - "n": "o5cn3ljSRi6FaDEKTn0PS-oL9EFyv1pI7dRgffQLD1qf5D6sprmYfWWokSsrWig8u2y0HChSygR6Jn5KXBqQn6FpM0dDJLnWQDRXHLl3Ey1iPYgDSmOIsIGrV9ZyNCQwk03wAgWbfdBTig3QSDYD-sTNOs3pc4UD_PqAvU2nz_1SS2ZiOwOn5F6gulE1L0iE3KEUEvOIagfHNVhz0oxa_VRZILkzV-zr6R_TW1m97h4H8jXl_VJyQGyhMGGypuDrQ9_vaY_RLEulLCyY0INglHWQ7pckxBtI5q55-Vio2wgewe2_qYcGsnBGaDNbySAsvYcWRrqDiFyzrJYivodqTQ" - } - ] - }''', -} diff --git a/lms/envs/common.py b/lms/envs/common.py index 8c966e67f6d0..b89f20e3fe85 100644 --- a/lms/envs/common.py +++ b/lms/envs/common.py @@ -4320,6 +4320,12 @@ def _make_locale_paths(settings): # pylint: disable=missing-function-docstring 'JWT_PUBLIC_SIGNING_JWK_SET': None, } +# NOTE: In order to create both JWT_PRIVATE_SIGNING_JWK and JWT_PUBLIC_SIGNING_JWK_SET, +# start devstack on an lms shell and then run the command: +# > python manage.py lms generate_jwt_signing_key +# This will output asymmetric JWTs to use here. Read more on this on: +# https://github.com/openedx/edx-platform/blob/master/openedx/core/djangoapps/oauth_dispatch/docs/decisions/0008-use-asymmetric-jwts.rst + COURSE_CATALOG_URL_ROOT = 'http://localhost:8008' COURSE_CATALOG_API_URL = f'{COURSE_CATALOG_URL_ROOT}/api/v1' diff --git a/openedx/core/lib/tests/test_jwt.py b/openedx/core/lib/tests/test_jwt.py index 79caf0207fa1..7a678dd3c09b 100644 --- a/openedx/core/lib/tests/test_jwt.py +++ b/openedx/core/lib/tests/test_jwt.py @@ -7,6 +7,7 @@ from jwkest import BadSignature, Expired, Invalid, MissingKey, jwk from jwkest.jws import JWS +from openedx.core.djangolib.testing.utils import skip_unless_lms from openedx.core.lib.jwt import _encode_and_sign, create_jwt, unpack_jwt @@ -24,6 +25,7 @@ } +@skip_unless_lms class TestSign(unittest.TestCase): """ Tests for JWT creation and signing. @@ -66,6 +68,7 @@ def _verify_jwt(jwt_token): return decoded +@skip_unless_lms class TestUnpack(unittest.TestCase): """ Tests for JWT unpacking. diff --git a/requirements/edx/base.txt b/requirements/edx/base.txt index c8f2af138c64..bd412f92132a 100644 --- a/requirements/edx/base.txt +++ b/requirements/edx/base.txt @@ -80,6 +80,9 @@ boto3==1.36.3 # ora2 botocore==1.36.3 # via + # -r requirements/edx/kernel.in + # boto3 + # s3transfer bridgekeeper==0.9 # via -r requirements/edx/kernel.in cachecontrol==0.14.2 @@ -534,8 +537,6 @@ edx-toggles==5.2.0 # edxval # event-tracking # ora2 -edx-token-utils==0.2.1 - # via -r requirements/edx/kernel.in edx-when==2.5.1 # via # -r requirements/edx/kernel.in diff --git a/requirements/edx/development.txt b/requirements/edx/development.txt index 61305f1cbc5b..6a8c617d41be 100644 --- a/requirements/edx/development.txt +++ b/requirements/edx/development.txt @@ -844,10 +844,6 @@ edx-toggles==5.2.0 # edxval # event-tracking # ora2 -edx-token-utils==0.2.1 - # via - # -r requirements/edx/doc.txt - # -r requirements/edx/testing.txt edx-when==2.5.1 # via # -r requirements/edx/doc.txt diff --git a/requirements/edx/doc.txt b/requirements/edx/doc.txt index c08b5b6fb396..66bd24f8fcfc 100644 --- a/requirements/edx/doc.txt +++ b/requirements/edx/doc.txt @@ -628,8 +628,6 @@ edx-toggles==5.2.0 # edxval # event-tracking # ora2 -edx-token-utils==0.2.1 - # via -r requirements/edx/base.txt edx-when==2.5.1 # via # -r requirements/edx/base.txt diff --git a/requirements/edx/testing.txt b/requirements/edx/testing.txt index ae5aed4234f4..27fe32c152b2 100644 --- a/requirements/edx/testing.txt +++ b/requirements/edx/testing.txt @@ -651,8 +651,6 @@ edx-toggles==5.2.0 # edxval # event-tracking # ora2 -edx-token-utils==0.2.1 - # via -r requirements/edx/base.txt edx-when==2.5.1 # via # -r requirements/edx/base.txt From dd86710d9d41d92506881ba3009d1be2b6ebad74 Mon Sep 17 00:00:00 2001 From: Feanil Patel Date: Thu, 23 Jan 2025 16:16:19 -0500 Subject: [PATCH 9/9] docs: Update lms/envs/common.py --- lms/envs/common.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lms/envs/common.py b/lms/envs/common.py index b89f20e3fe85..76127d062d81 100644 --- a/lms/envs/common.py +++ b/lms/envs/common.py @@ -4321,7 +4321,7 @@ def _make_locale_paths(settings): # pylint: disable=missing-function-docstring } # NOTE: In order to create both JWT_PRIVATE_SIGNING_JWK and JWT_PUBLIC_SIGNING_JWK_SET, -# start devstack on an lms shell and then run the command: +# in an lms shell run the following command: # > python manage.py lms generate_jwt_signing_key # This will output asymmetric JWTs to use here. Read more on this on: # https://github.com/openedx/edx-platform/blob/master/openedx/core/djangoapps/oauth_dispatch/docs/decisions/0008-use-asymmetric-jwts.rst