From c24bf4e7cf589a4fc3d0bff44339deda49354f47 Mon Sep 17 00:00:00 2001 From: Travis McKinney Date: Wed, 9 Oct 2024 20:39:20 +0000 Subject: [PATCH 1/6] support custom oauth2 models --- common/djangoapps/entitlements/rest_api/v1/serializers.py | 4 ++-- openedx/core/djangoapps/user_authn/cookies.py | 7 ++++++- openedx/core/lib/api/authentication.py | 8 +++++++- 3 files changed, 15 insertions(+), 4 deletions(-) diff --git a/common/djangoapps/entitlements/rest_api/v1/serializers.py b/common/djangoapps/entitlements/rest_api/v1/serializers.py index a64d6d25bb31..46bf2345a955 100644 --- a/common/djangoapps/entitlements/rest_api/v1/serializers.py +++ b/common/djangoapps/entitlements/rest_api/v1/serializers.py @@ -50,7 +50,7 @@ class CourseEntitlementSupportDetailSerializer(serializers.ModelSerializer): slug_field='username', default=serializers.CurrentUserDefault() ) - unenrolled_run = CourseKeyField('unenrolled_run.id') + # unenrolled_run = CourseKeyField('unenrolled_run.id') class Meta: model = CourseEntitlementSupportDetail @@ -58,6 +58,6 @@ class Meta: 'support_user', 'action', 'comments', - 'unenrolled_run', + # 'unenrolled_run', 'created' ) diff --git a/openedx/core/djangoapps/user_authn/cookies.py b/openedx/core/djangoapps/user_authn/cookies.py index 24f929698fa7..4cc0a9f0b7e8 100644 --- a/openedx/core/djangoapps/user_authn/cookies.py +++ b/openedx/core/djangoapps/user_authn/cookies.py @@ -14,7 +14,10 @@ from django.utils.http import http_date, parse_http_date from edx_rest_framework_extensions.auth.jwt import cookies as jwt_cookies from edx_rest_framework_extensions.auth.jwt.constants import JWT_DELIMITER -from oauth2_provider.models import Application +# @medality_custom start +# from oauth2_provider.models import Application +from django.apps import apps +# @medality_custom end from common.djangoapps.student.models import UserProfile from openedx.core.djangoapps.oauth_dispatch.adapters import DOTAdapter @@ -354,6 +357,8 @@ def _get_login_oauth_client(): Returns the configured OAuth Client/Application used for Login. """ login_client_id = settings.JWT_AUTH['JWT_LOGIN_CLIENT_ID'] + # @medality_custom + Application = apps.get_model(settings.OAUTH2_PROVIDER_APPLICATION_MODEL) try: return Application.objects.get(client_id=login_client_id) except Application.DoesNotExist: diff --git a/openedx/core/lib/api/authentication.py b/openedx/core/lib/api/authentication.py index a762d398b378..c074773a8ff8 100644 --- a/openedx/core/lib/api/authentication.py +++ b/openedx/core/lib/api/authentication.py @@ -3,6 +3,10 @@ import logging import django.utils.timezone +# @medality_custom start +from django.apps import apps +from django.conf import settings +# @medality_custom end from oauth2_provider import models as dot_models from rest_framework.exceptions import AuthenticationFailed from rest_framework.authentication import BaseAuthentication, get_authorization_header @@ -92,7 +96,9 @@ def authenticate_credentials(self, access_token): }) else: user = token.user - has_application = dot_models.Application.objects.filter(user_id=user.id) + # @medality_custom start + Application = apps.get_model(settings.OAUTH2_PROVIDER_APPLICATION_MODEL) + has_application = Application.objects.filter(user_id=user.id) if not user.has_usable_password() and not has_application: msg = 'User disabled by admin: %s' % user.get_username() raise AuthenticationFailed({ From 8d33a33abf483e0a7e1c38eea7dfb8a2e1756584 Mon Sep 17 00:00:00 2001 From: Travis McKinney Date: Mon, 21 Oct 2024 20:30:56 +0000 Subject: [PATCH 2/6] add support for custom AccessToken model --- .../djangoapps/oauth_dispatch/adapters/dot.py | 6 ++++-- openedx/core/djangoapps/oauth_dispatch/admin.py | 4 ++-- .../oauth_dispatch/dot_overrides/validators.py | 12 ++++++++---- openedx/core/lib/api/authentication.py | 16 +++++++++------- 4 files changed, 23 insertions(+), 15 deletions(-) diff --git a/openedx/core/djangoapps/oauth_dispatch/adapters/dot.py b/openedx/core/djangoapps/oauth_dispatch/adapters/dot.py index 384d6f9760d9..c96b0465f4b8 100644 --- a/openedx/core/djangoapps/oauth_dispatch/adapters/dot.py +++ b/openedx/core/djangoapps/oauth_dispatch/adapters/dot.py @@ -65,14 +65,16 @@ def get_access_token(self, token_string): """ Given a token string, return the matching AccessToken object. """ - return models.AccessToken.objects.get(token=token_string) + # @medality_custom + return models.get_access_token_model().objects.get(token=token_string) def create_access_token_for_test(self, token_string, client, user, expires): """ Returns a new AccessToken object created from the given arguments. This method is currently used only by tests. """ - return models.AccessToken.objects.create( + # @medality_custom + return models.get_access_token_model().objects.create( token=token_string, application=client, user=user, diff --git a/openedx/core/djangoapps/oauth_dispatch/admin.py b/openedx/core/djangoapps/oauth_dispatch/admin.py index 333d4a3d4f2f..6316937df053 100644 --- a/openedx/core/djangoapps/oauth_dispatch/admin.py +++ b/openedx/core/djangoapps/oauth_dispatch/admin.py @@ -29,8 +29,8 @@ def decorator(cls): return decorator - -@reregister(models.AccessToken) +# @medality_custom +@reregister(models.get_access_token_model()) class DOTAccessTokenAdmin(ModelAdmin): """ Custom AccessToken Admin diff --git a/openedx/core/djangoapps/oauth_dispatch/dot_overrides/validators.py b/openedx/core/djangoapps/oauth_dispatch/dot_overrides/validators.py index 0b2ad35b2f42..0b319ea69bb7 100644 --- a/openedx/core/djangoapps/oauth_dispatch/dot_overrides/validators.py +++ b/openedx/core/djangoapps/oauth_dispatch/dot_overrides/validators.py @@ -8,7 +8,8 @@ from django.contrib.auth import authenticate, get_user_model from django.db.models.signals import pre_save from django.dispatch import receiver -from oauth2_provider.models import AccessToken +# @medality_custom +from oauth2_provider import models from oauth2_provider.oauth2_validators import OAuth2Validator from oauth2_provider.scopes import get_scopes_backend from pytz import utc @@ -17,7 +18,8 @@ # pylint: disable=W0223 -@receiver(pre_save, sender=AccessToken) +# @medality_custom +@receiver(pre_save, sender=models.get_access_token_model()) def on_access_token_presave(sender, instance, *args, **kwargs): # pylint: disable=unused-argument """ Mark AccessTokens as expired for 'restricted applications' if required. @@ -108,7 +110,8 @@ def _update_token_expiry_if_restricted_client(self, token, client): # and calculate expires_in (in seconds) from the database value. This # value should be a negative value, meaning that it is already expired. if RestrictedApplication.should_expire_access_token(client): - access_token = AccessToken.objects.get(token=token['access_token']) + # @medality_custom + access_token = models.get_access_token_model().objects.get(token=token['access_token']) expires_in = (access_token.expires - _get_utc_now()).total_seconds() assert expires_in < 0 token['expires_in'] = expires_in @@ -126,7 +129,8 @@ def _update_token_expiry_if_overridden_in_request(self, token, request): """ expires_in = getattr(request, 'expires_in', None) if expires_in: - access_token = AccessToken.objects.get(token=token['access_token']) + # @medality_custom + access_token = models.get_access_token_model().objects.get(token=token['access_token']) access_token.expires = _get_utc_now() + timedelta(seconds=expires_in) access_token.save() token['expires_in'] = expires_in diff --git a/openedx/core/lib/api/authentication.py b/openedx/core/lib/api/authentication.py index c074773a8ff8..7acc47dc4bb3 100644 --- a/openedx/core/lib/api/authentication.py +++ b/openedx/core/lib/api/authentication.py @@ -3,10 +3,6 @@ import logging import django.utils.timezone -# @medality_custom start -from django.apps import apps -from django.conf import settings -# @medality_custom end from oauth2_provider import models as dot_models from rest_framework.exceptions import AuthenticationFailed from rest_framework.authentication import BaseAuthentication, get_authorization_header @@ -97,8 +93,13 @@ def authenticate_credentials(self, access_token): else: user = token.user # @medality_custom start - Application = apps.get_model(settings.OAUTH2_PROVIDER_APPLICATION_MODEL) - has_application = Application.objects.filter(user_id=user.id) + if not token.is_valid(): + raise AuthenticationFailed({ + 'error_code': OAUTH2_TOKEN_ERROR, + 'developer_message': 'The provided access token is not valid.' + }) + has_application = dot_models.get_application_model().objects.filter(user_id=user.id) + # @medality_custom end if not user.has_usable_password() and not has_application: msg = 'User disabled by admin: %s' % user.get_username() raise AuthenticationFailed({ @@ -122,7 +123,8 @@ def get_access_token(self, access_token): Return a valid access token stored by django-oauth-toolkit (DOT), or None if no matching token is found. """ - token_query = dot_models.AccessToken.objects.select_related('user') + # @medality_custom + token_query = dot_models.get_access_token_model().objects.select_related('user') return token_query.filter(token=access_token).first() def authenticate_header(self, request): From bb96e50ce3997a20cad70fb98ae0ea2653989a30 Mon Sep 17 00:00:00 2001 From: Travis McKinney Date: Mon, 21 Oct 2024 20:52:51 +0000 Subject: [PATCH 3/6] revert unrelated change --- common/djangoapps/entitlements/rest_api/v1/serializers.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/common/djangoapps/entitlements/rest_api/v1/serializers.py b/common/djangoapps/entitlements/rest_api/v1/serializers.py index 46bf2345a955..a64d6d25bb31 100644 --- a/common/djangoapps/entitlements/rest_api/v1/serializers.py +++ b/common/djangoapps/entitlements/rest_api/v1/serializers.py @@ -50,7 +50,7 @@ class CourseEntitlementSupportDetailSerializer(serializers.ModelSerializer): slug_field='username', default=serializers.CurrentUserDefault() ) - # unenrolled_run = CourseKeyField('unenrolled_run.id') + unenrolled_run = CourseKeyField('unenrolled_run.id') class Meta: model = CourseEntitlementSupportDetail @@ -58,6 +58,6 @@ class Meta: 'support_user', 'action', 'comments', - # 'unenrolled_run', + 'unenrolled_run', 'created' ) From b5266bf7d6636ca06af097ed613aadf3685ed5c8 Mon Sep 17 00:00:00 2001 From: Travis McKinney Date: Mon, 21 Oct 2024 20:54:46 +0000 Subject: [PATCH 4/6] cleanup --- openedx/core/djangoapps/user_authn/cookies.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/openedx/core/djangoapps/user_authn/cookies.py b/openedx/core/djangoapps/user_authn/cookies.py index 4cc0a9f0b7e8..404458e5c2ce 100644 --- a/openedx/core/djangoapps/user_authn/cookies.py +++ b/openedx/core/djangoapps/user_authn/cookies.py @@ -14,10 +14,8 @@ from django.utils.http import http_date, parse_http_date from edx_rest_framework_extensions.auth.jwt import cookies as jwt_cookies from edx_rest_framework_extensions.auth.jwt.constants import JWT_DELIMITER -# @medality_custom start -# from oauth2_provider.models import Application -from django.apps import apps -# @medality_custom end +# @medality_custom +from oauth2_provider import models as oauth_models from common.djangoapps.student.models import UserProfile from openedx.core.djangoapps.oauth_dispatch.adapters import DOTAdapter @@ -358,7 +356,7 @@ def _get_login_oauth_client(): """ login_client_id = settings.JWT_AUTH['JWT_LOGIN_CLIENT_ID'] # @medality_custom - Application = apps.get_model(settings.OAUTH2_PROVIDER_APPLICATION_MODEL) + Application = oauth_models.get_application_model() try: return Application.objects.get(client_id=login_client_id) except Application.DoesNotExist: From 3aace51d3dd765b5579c2f761672e5384d1bb479 Mon Sep 17 00:00:00 2001 From: Travis McKinney Date: Wed, 23 Oct 2024 20:12:46 +0000 Subject: [PATCH 5/6] fix serializer --- common/djangoapps/entitlements/rest_api/v1/serializers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/djangoapps/entitlements/rest_api/v1/serializers.py b/common/djangoapps/entitlements/rest_api/v1/serializers.py index a64d6d25bb31..85e1f474d8f2 100644 --- a/common/djangoapps/entitlements/rest_api/v1/serializers.py +++ b/common/djangoapps/entitlements/rest_api/v1/serializers.py @@ -50,7 +50,7 @@ class CourseEntitlementSupportDetailSerializer(serializers.ModelSerializer): slug_field='username', default=serializers.CurrentUserDefault() ) - unenrolled_run = CourseKeyField('unenrolled_run.id') + unenrolled_run = CourseKeyField(source='unenrolled_run.id') class Meta: model = CourseEntitlementSupportDetail From 5a7b25b96fa83696392fe244995a8a134de832de Mon Sep 17 00:00:00 2001 From: Travis McKinney Date: Wed, 30 Oct 2024 21:22:18 +0000 Subject: [PATCH 6/6] add @medality_custom tag --- common/djangoapps/entitlements/rest_api/v1/serializers.py | 1 + 1 file changed, 1 insertion(+) diff --git a/common/djangoapps/entitlements/rest_api/v1/serializers.py b/common/djangoapps/entitlements/rest_api/v1/serializers.py index 85e1f474d8f2..580550b021c4 100644 --- a/common/djangoapps/entitlements/rest_api/v1/serializers.py +++ b/common/djangoapps/entitlements/rest_api/v1/serializers.py @@ -50,6 +50,7 @@ class CourseEntitlementSupportDetailSerializer(serializers.ModelSerializer): slug_field='username', default=serializers.CurrentUserDefault() ) + # @medality_custom: this was a fix to a syntax error unenrolled_run = CourseKeyField(source='unenrolled_run.id') class Meta: