diff --git a/common/djangoapps/course_modes/api/v1/views.py b/common/djangoapps/course_modes/api/v1/views.py index 641ecb74c4..7085b5233b 100644 --- a/common/djangoapps/course_modes/api/v1/views.py +++ b/common/djangoapps/course_modes/api/v1/views.py @@ -16,7 +16,7 @@ from rest_framework.response import Response from course_modes.api.serializers import CourseModeSerializer from course_modes.models import CourseMode -from openedx.core.lib.api.authentication import OAuth2AuthenticationAllowInactiveUser +from openedx.core.lib.api.authentication import OAuth2AuthenticationAllowInactiveUserDeprecated from openedx.core.lib.api.parsers import MergePatchParser log = logging.getLogger(__name__) @@ -29,7 +29,7 @@ class CourseModesMixin(object): """ authentication_classes = ( JwtAuthentication, - OAuth2AuthenticationAllowInactiveUser, + OAuth2AuthenticationAllowInactiveUserDeprecated, SessionAuthenticationAllowInactiveUser, ) # When not considering JWT conditions, this permission class grants access diff --git a/common/djangoapps/third_party_auth/api/views.py b/common/djangoapps/third_party_auth/api/views.py index 076f74b68e..462dd907cc 100644 --- a/common/djangoapps/third_party_auth/api/views.py +++ b/common/djangoapps/third_party_auth/api/views.py @@ -18,7 +18,10 @@ from rest_framework.response import Response from rest_framework.views import APIView from social_django.models import UserSocialAuth -from openedx.core.lib.api.authentication import OAuth2AuthenticationDeprecated, OAuth2AuthenticationAllowInactiveUser +from openedx.core.lib.api.authentication import ( + OAuth2AuthenticationDeprecated, + OAuth2AuthenticationAllowInactiveUserDeprecated +) from openedx.core.lib.api.permissions import ApiKeyHeaderPermission from third_party_auth import pipeline from third_party_auth.api import serializers @@ -64,7 +67,7 @@ class BaseUserView(APIView): authentication_classes = ( # Users may want to view/edit the providers used for authentication before they've # activated their account, so we allow inactive users. - OAuth2AuthenticationAllowInactiveUser, + OAuth2AuthenticationAllowInactiveUserDeprecated, SessionAuthenticationAllowInactiveUser, ) throttle_classes = [ProviderSustainedThrottle, ProviderBurstThrottle] @@ -399,7 +402,7 @@ class ThirdPartyAuthUserStatusView(APIView): user with respect to the third party auth providers configured in the system. """ authentication_classes = ( - JwtAuthentication, OAuth2AuthenticationAllowInactiveUser, SessionAuthenticationAllowInactiveUser + JwtAuthentication, OAuth2AuthenticationAllowInactiveUserDeprecated, SessionAuthenticationAllowInactiveUser ) permission_classes = (permissions.IsAuthenticated,) diff --git a/lms/djangoapps/badges/api/views.py b/lms/djangoapps/badges/api/views.py index 174a240e68..a8f5a50879 100644 --- a/lms/djangoapps/badges/api/views.py +++ b/lms/djangoapps/badges/api/views.py @@ -12,7 +12,7 @@ from rest_framework.exceptions import APIException from badges.models import BadgeAssertion from openedx.core.djangoapps.user_api.permissions import is_field_shared_factory -from openedx.core.lib.api.authentication import OAuth2AuthenticationAllowInactiveUser +from openedx.core.lib.api.authentication import OAuth2AuthenticationAllowInactiveUserDeprecated from .serializers import BadgeAssertionSerializer @@ -94,7 +94,7 @@ class UserBadgeAssertions(generics.ListAPIView): """ serializer_class = BadgeAssertionSerializer authentication_classes = ( - OAuth2AuthenticationAllowInactiveUser, + OAuth2AuthenticationAllowInactiveUserDeprecated, SessionAuthenticationAllowInactiveUser ) permission_classes = (is_field_shared_factory("accomplishments_shared"),) diff --git a/lms/djangoapps/ccx/api/v0/views.py b/lms/djangoapps/ccx/api/v0/views.py index a6181002b4..c9bbdad503 100644 --- a/lms/djangoapps/ccx/api/v0/views.py +++ b/lms/djangoapps/ccx/api/v0/views.py @@ -353,7 +353,7 @@ class CCXListView(GenericAPIView): """ authentication_classes = ( JwtAuthentication, - authentication.OAuth2AuthenticationAllowInactiveUser, + authentication.OAuth2AuthenticationAllowInactiveUserDeprecated, SessionAuthenticationAllowInactiveUser, ) permission_classes = (IsAuthenticated, permissions.IsMasterCourseStaffInstructor) @@ -612,7 +612,7 @@ class CCXDetailView(GenericAPIView): authentication_classes = ( JwtAuthentication, - authentication.OAuth2AuthenticationAllowInactiveUser, + authentication.OAuth2AuthenticationAllowInactiveUserDeprecated, SessionAuthenticationAllowInactiveUser, ) permission_classes = (IsAuthenticated, permissions.IsCourseStaffInstructor) diff --git a/lms/djangoapps/certificates/apis/v0/views.py b/lms/djangoapps/certificates/apis/v0/views.py index 305ace0420..6a85ffa354 100644 --- a/lms/djangoapps/certificates/apis/v0/views.py +++ b/lms/djangoapps/certificates/apis/v0/views.py @@ -21,7 +21,7 @@ from lms.djangoapps.certificates.api import get_certificate_for_user, get_certif from openedx.core.djangoapps.certificates.api import certificates_viewable_for_course from openedx.core.djangoapps.content.course_overviews.models import CourseOverview from openedx.core.djangoapps.user_api.accounts.api import visible_fields -from openedx.core.lib.api.authentication import OAuth2AuthenticationAllowInactiveUser +from openedx.core.lib.api.authentication import OAuth2AuthenticationAllowInactiveUserDeprecated log = logging.getLogger(__name__) @@ -85,7 +85,7 @@ class CertificatesDetailView(GenericAPIView): authentication_classes = ( JwtAuthentication, - OAuth2AuthenticationAllowInactiveUser, + OAuth2AuthenticationAllowInactiveUserDeprecated, SessionAuthenticationAllowInactiveUser, ) @@ -147,7 +147,7 @@ class CertificatesListView(GenericAPIView): """REST API endpoints for listing certificates.""" authentication_classes = ( JwtAuthentication, - OAuth2AuthenticationAllowInactiveUser, + OAuth2AuthenticationAllowInactiveUserDeprecated, SessionAuthenticationAllowInactiveUser, ) diff --git a/lms/djangoapps/commerce/api/v0/views.py b/lms/djangoapps/commerce/api/v0/views.py index f73a028594..6a28122c70 100644 --- a/lms/djangoapps/commerce/api/v0/views.py +++ b/lms/djangoapps/commerce/api/v0/views.py @@ -23,7 +23,7 @@ from openedx.core.djangoapps.embargo import api as embargo_api from openedx.core.djangoapps.enrollments.api import add_enrollment from openedx.core.djangoapps.enrollments.views import EnrollmentCrossDomainSessionAuth from openedx.core.djangoapps.user_api.preferences.api import update_email_opt_in -from openedx.core.lib.api.authentication import OAuth2AuthenticationAllowInactiveUser +from openedx.core.lib.api.authentication import OAuth2AuthenticationAllowInactiveUserDeprecated from student.models import CourseEnrollment from student.signals import SAILTHRU_AUDIT_PURCHASE from util.json_request import JsonResponse @@ -40,7 +40,7 @@ class BasketsView(APIView): # LMS utilizes User.user_is_active to indicate email verification, not whether an account is active. Sigh! authentication_classes = (JwtAuthentication, - OAuth2AuthenticationAllowInactiveUser, + OAuth2AuthenticationAllowInactiveUserDeprecated, EnrollmentCrossDomainSessionAuth) permission_classes = (IsAuthenticated,) diff --git a/lms/djangoapps/courseware/module_render.py b/lms/djangoapps/courseware/module_render.py index c99e85ec4b..30acc258e3 100644 --- a/lms/djangoapps/courseware/module_render.py +++ b/lms/djangoapps/courseware/module_render.py @@ -68,7 +68,7 @@ from openedx.core.djangoapps.crawlers.models import CrawlersConfig from openedx.core.djangoapps.credit.services import CreditService from openedx.core.djangoapps.util.user_utils import SystemUser from openedx.core.djangolib.markup import HTML -from openedx.core.lib.api.authentication import OAuth2AuthenticationAllowInactiveUser +from openedx.core.lib.api.authentication import OAuth2AuthenticationAllowInactiveUserDeprecated from openedx.core.lib.api.view_utils import view_auth_classes from openedx.core.lib.gating.services import GatingService from openedx.core.lib.license import wrap_with_license @@ -1033,7 +1033,7 @@ def handle_xblock_callback(request, course_id, usage_id, handler, suffix=None): # to avoid introducing backwards-incompatible changes. # You can see https://github.com/edx/XBlock/pull/383 for more details. else: - authentication_classes = (JwtAuthentication, OAuth2AuthenticationAllowInactiveUser) + authentication_classes = (JwtAuthentication, OAuth2AuthenticationAllowInactiveUserDeprecated) authenticators = [auth() for auth in authentication_classes] for authenticator in authenticators: diff --git a/lms/djangoapps/discussion/rest_api/views.py b/lms/djangoapps/discussion/rest_api/views.py index f3a10af27f..9c3af50497 100644 --- a/lms/djangoapps/discussion/rest_api/views.py +++ b/lms/djangoapps/discussion/rest_api/views.py @@ -5,6 +5,7 @@ Discussion API views import logging +from django.conf import settings from django.contrib.auth.models import User from django.core.exceptions import ValidationError from edx_rest_framework_extensions.auth.jwt.authentication import JwtAuthentication @@ -55,7 +56,10 @@ from openedx.core.djangoapps.django_comment_common.utils import ( ) from openedx.core.djangoapps.user_api.accounts.permissions import CanReplaceUsername, CanRetireUser from openedx.core.djangoapps.user_api.models import UserRetirementStatus -from openedx.core.lib.api.authentication import OAuth2AuthenticationAllowInactiveUser +from openedx.core.lib.api.authentication import ( + OAuth2AuthenticationAllowInactiveUserDeprecated, + OAuth2AuthenticationAllowInactiveUser +) from openedx.core.lib.api.parsers import MergePatchParser from openedx.core.lib.api.view_utils import DeveloperErrorViewMixin, view_auth_classes from util.json_request import JsonResponse @@ -63,6 +67,30 @@ from xmodule.modulestore.django import modulestore log = logging.getLogger(__name__) +# .. toggle_name: DISCUSSION_USE_NEW_OAUTH2_CLASS +# .. toggle_implementation: DjangoSetting +# .. toggle_default: False +# .. toggle_description: Toggle for replacing a deprecated class with its replacement +# .. toggle_category: n/a +# .. toggle_use_cases: Monitored Rollout +# .. toggle_creation_date: 2020-01-31 +# .. toggle_expiration_date: 2020-02-28 +# .. toggle_warnings: None +# .. toggle_tickets: BOM-1037 +# .. toggle_status: supported +if getattr(settings, "DISCUSSION_USE_NEW_OAUTH2_CLASS", False): + _discussion_configured_authentication_classes = ( + JwtAuthentication, + OAuth2AuthenticationAllowInactiveUser, + SessionAuthenticationAllowInactiveUser, + ) +else: + _discussion_configured_authentication_classes = ( + JwtAuthentication, + OAuth2AuthenticationAllowInactiveUserDeprecated, + SessionAuthenticationAllowInactiveUser, + ) + @view_auth_classes() class CourseView(DeveloperErrorViewMixin, APIView): @@ -749,11 +777,7 @@ class CourseDiscussionSettingsAPIView(DeveloperErrorViewMixin, APIView): * available_division_schemes: A list of available division schemes for the course. """ - authentication_classes = ( - JwtAuthentication, - OAuth2AuthenticationAllowInactiveUser, - SessionAuthenticationAllowInactiveUser, - ) + authentication_classes = _discussion_configured_authentication_classes parser_classes = (JSONParser, MergePatchParser,) permission_classes = (permissions.IsAuthenticated, permissions.IsAdminUser) @@ -884,11 +908,7 @@ class CourseDiscussionRolesAPIView(DeveloperErrorViewMixin, APIView): * division_scheme: The division scheme used by the course. """ - authentication_classes = ( - JwtAuthentication, - OAuth2AuthenticationAllowInactiveUser, - SessionAuthenticationAllowInactiveUser, - ) + authentication_classes = _discussion_configured_authentication_classes permission_classes = (permissions.IsAuthenticated, permissions.IsAdminUser) def _get_request_kwargs(self, course_id, rolename): diff --git a/lms/djangoapps/experiments/views_custom.py b/lms/djangoapps/experiments/views_custom.py index 99d20d8fac..06839a93c3 100644 --- a/lms/djangoapps/experiments/views_custom.py +++ b/lms/djangoapps/experiments/views_custom.py @@ -19,7 +19,7 @@ from rest_framework.views import APIView from openedx.core.djangoapps.content.course_overviews.models import CourseOverview from openedx.core.djangoapps.cors_csrf.decorators import ensure_csrf_cookie_cross_domain from openedx.core.djangoapps.waffle_utils import WaffleFlag, WaffleFlagNamespace -from openedx.core.lib.api.authentication import OAuth2AuthenticationAllowInactiveUser +from openedx.core.lib.api.authentication import OAuth2AuthenticationAllowInactiveUserDeprecated from openedx.core.lib.api.permissions import ApiKeyHeaderPermissionIsAuthenticated from openedx.core.lib.api.view_utils import DeveloperErrorViewMixin @@ -100,7 +100,7 @@ class Rev934(DeveloperErrorViewMixin, APIView): authentication_classes = ( JwtAuthentication, - OAuth2AuthenticationAllowInactiveUser, + OAuth2AuthenticationAllowInactiveUserDeprecated, SessionAuthenticationAllowInactiveUser, ) permission_classes = (ApiKeyHeaderPermissionIsAuthenticated,) diff --git a/lms/djangoapps/grades/rest_api/v1/views.py b/lms/djangoapps/grades/rest_api/v1/views.py index 5bb66b5325..118326c81b 100644 --- a/lms/djangoapps/grades/rest_api/v1/views.py +++ b/lms/djangoapps/grades/rest_api/v1/views.py @@ -16,7 +16,7 @@ from lms.djangoapps.courseware.access import has_access from lms.djangoapps.grades.api import CourseGradeFactory, clear_prefetched_course_grades, prefetch_course_grades from lms.djangoapps.grades.rest_api.serializers import GradingPolicySerializer from lms.djangoapps.grades.rest_api.v1.utils import CourseEnrollmentPagination, GradeViewMixin -from openedx.core.lib.api.authentication import OAuth2AuthenticationAllowInactiveUser +from openedx.core.lib.api.authentication import OAuth2AuthenticationAllowInactiveUserDeprecated from openedx.core.lib.api.view_utils import PaginatedAPIView, get_course_key, verify_course_exists from xmodule.modulestore.django import modulestore @@ -91,7 +91,7 @@ class CourseGradesView(GradeViewMixin, PaginatedAPIView): """ authentication_classes = ( JwtAuthentication, - OAuth2AuthenticationAllowInactiveUser, + OAuth2AuthenticationAllowInactiveUserDeprecated, SessionAuthenticationAllowInactiveUser, ) @@ -171,7 +171,7 @@ class CourseGradingPolicy(GradeViewMixin, ListAPIView): authentication_classes = ( JwtAuthentication, - OAuth2AuthenticationAllowInactiveUser, + OAuth2AuthenticationAllowInactiveUserDeprecated, SessionAuthenticationAllowInactiveUser, ) diff --git a/lms/djangoapps/instructor/views/api.py b/lms/djangoapps/instructor/views/api.py index e876611eca..73df7ff8fc 100644 --- a/lms/djangoapps/instructor/views/api.py +++ b/lms/djangoapps/instructor/views/api.py @@ -94,7 +94,7 @@ from openedx.core.djangoapps.django_comment_common.models import ( from openedx.core.djangoapps.site_configuration import helpers as configuration_helpers from openedx.core.djangoapps.user_api.preferences.api import get_user_preference, set_user_preference from openedx.core.djangolib.markup import HTML, Text -from openedx.core.lib.api.authentication import OAuth2AuthenticationAllowInactiveUser +from openedx.core.lib.api.authentication import OAuth2AuthenticationAllowInactiveUserDeprecated from openedx.core.lib.api.view_utils import DeveloperErrorViewMixin from shoppingcart.models import ( Coupon, @@ -1474,7 +1474,7 @@ class CohortCSV(DeveloperErrorViewMixin, APIView): """ authentication_classes = ( JwtAuthentication, - OAuth2AuthenticationAllowInactiveUser, + OAuth2AuthenticationAllowInactiveUserDeprecated, SessionAuthenticationAllowInactiveUser, ) permission_classes = (permissions.IsAuthenticated, permissions.IsAdminUser) diff --git a/lms/djangoapps/program_enrollments/rest_api/v1/views.py b/lms/djangoapps/program_enrollments/rest_api/v1/views.py index 277cac8d4c..5e338d9c30 100644 --- a/lms/djangoapps/program_enrollments/rest_api/v1/views.py +++ b/lms/djangoapps/program_enrollments/rest_api/v1/views.py @@ -47,7 +47,7 @@ from openedx.core.djangoapps.catalog.utils import ( normalize_program_type ) from openedx.core.djangoapps.content.course_overviews.models import CourseOverview -from openedx.core.lib.api.authentication import OAuth2AuthenticationAllowInactiveUser +from openedx.core.lib.api.authentication import OAuth2AuthenticationAllowInactiveUserDeprecated from openedx.core.lib.api.view_utils import DeveloperErrorViewMixin, PaginatedAPIView from student.helpers import get_resume_urls_for_enrollments from student.models import CourseEnrollment @@ -328,7 +328,7 @@ class ProgramEnrollmentsView( """ authentication_classes = ( JwtAuthentication, - OAuth2AuthenticationAllowInactiveUser, + OAuth2AuthenticationAllowInactiveUserDeprecated, SessionAuthenticationAllowInactiveUser, ) permission_classes = (permissions.JWT_RESTRICTED_APPLICATION_OR_USER_ACCESS,) @@ -472,7 +472,7 @@ class ProgramCourseEnrollmentsView( """ authentication_classes = ( JwtAuthentication, - OAuth2AuthenticationAllowInactiveUser, + OAuth2AuthenticationAllowInactiveUserDeprecated, SessionAuthenticationAllowInactiveUser, ) permission_classes = (permissions.JWT_RESTRICTED_APPLICATION_OR_USER_ACCESS,) @@ -614,7 +614,7 @@ class ProgramCourseGradesView( """ authentication_classes = ( JwtAuthentication, - OAuth2AuthenticationAllowInactiveUser, + OAuth2AuthenticationAllowInactiveUserDeprecated, SessionAuthenticationAllowInactiveUser, ) permission_classes = (permissions.JWT_RESTRICTED_APPLICATION_OR_USER_ACCESS,) @@ -695,7 +695,7 @@ class UserProgramReadOnlyAccessView(DeveloperErrorViewMixin, PaginatedAPIView): """ authentication_classes = ( JwtAuthentication, - OAuth2AuthenticationAllowInactiveUser, + OAuth2AuthenticationAllowInactiveUserDeprecated, SessionAuthenticationAllowInactiveUser, ) permission_classes = (IsAuthenticated,) @@ -874,7 +874,7 @@ class ProgramCourseEnrollmentOverviewView( """ authentication_classes = ( JwtAuthentication, - OAuth2AuthenticationAllowInactiveUser, + OAuth2AuthenticationAllowInactiveUserDeprecated, SessionAuthenticationAllowInactiveUser, ) permission_classes = (IsAuthenticated,) @@ -983,7 +983,7 @@ class EnrollmentDataResetView(APIView): """ authentication_classes = ( JwtAuthentication, - OAuth2AuthenticationAllowInactiveUser, + OAuth2AuthenticationAllowInactiveUserDeprecated, SessionAuthenticationAllowInactiveUser, ) permission_classes = (permissions.JWT_RESTRICTED_APPLICATION_OR_USER_ACCESS,) diff --git a/openedx/core/djangoapps/auth_exchange/views.py b/openedx/core/djangoapps/auth_exchange/views.py index 0602c6e3bc..023f6c37b0 100644 --- a/openedx/core/djangoapps/auth_exchange/views.py +++ b/openedx/core/djangoapps/auth_exchange/views.py @@ -30,7 +30,7 @@ from rest_framework.views import APIView from openedx.core.djangoapps.auth_exchange.forms import AccessTokenExchangeForm from openedx.core.djangoapps.oauth_dispatch import adapters from openedx.core.djangoapps.oauth_dispatch.api import create_dot_access_token -from openedx.core.lib.api.authentication import OAuth2AuthenticationAllowInactiveUser +from openedx.core.lib.api.authentication import OAuth2AuthenticationAllowInactiveUserDeprecated class AccessTokenExchangeBase(APIView): @@ -131,7 +131,7 @@ class LoginWithAccessTokenView(APIView): """ View for exchanging an access token for session cookies """ - authentication_classes = (OAuth2AuthenticationAllowInactiveUser,) + authentication_classes = (OAuth2AuthenticationAllowInactiveUserDeprecated,) permission_classes = (permissions.IsAuthenticated,) @staticmethod diff --git a/openedx/core/djangoapps/course_groups/views.py b/openedx/core/djangoapps/course_groups/views.py index 9b565e5141..b726427d8a 100644 --- a/openedx/core/djangoapps/course_groups/views.py +++ b/openedx/core/djangoapps/course_groups/views.py @@ -28,7 +28,7 @@ from rest_framework.serializers import Serializer from lms.djangoapps.courseware.courses import get_course, get_course_with_access from edxmako.shortcuts import render_to_response from openedx.core.djangoapps.course_groups.models import CohortMembership -from openedx.core.lib.api.authentication import OAuth2AuthenticationAllowInactiveUser +from openedx.core.lib.api.authentication import OAuth2AuthenticationAllowInactiveUserDeprecated from openedx.core.lib.api.view_utils import DeveloperErrorViewMixin from student.auth import has_course_author_access from util.json_request import JsonResponse, expect_json @@ -429,7 +429,7 @@ class APIPermissions(GenericAPIView): """ authentication_classes = ( JwtAuthentication, - OAuth2AuthenticationAllowInactiveUser, + OAuth2AuthenticationAllowInactiveUserDeprecated, SessionAuthenticationAllowInactiveUser, ) permission_classes = (permissions.IsAuthenticated, permissions.IsAdminUser) diff --git a/openedx/core/djangoapps/enrollments/views.py b/openedx/core/djangoapps/enrollments/views.py index cf2e73ffd2..66ee888034 100644 --- a/openedx/core/djangoapps/enrollments/views.py +++ b/openedx/core/djangoapps/enrollments/views.py @@ -30,7 +30,7 @@ from openedx.core.djangoapps.enrollments.serializers import CourseEnrollmentsApi from openedx.core.djangoapps.user_api.accounts.permissions import CanRetireUser from openedx.core.djangoapps.user_api.models import UserRetirementStatus from openedx.core.djangoapps.user_api.preferences.api import update_email_opt_in -from openedx.core.lib.api.authentication import OAuth2AuthenticationAllowInactiveUser +from openedx.core.lib.api.authentication import OAuth2AuthenticationAllowInactiveUserDeprecated from openedx.core.lib.api.permissions import ApiKeyHeaderPermission, ApiKeyHeaderPermissionIsAuthenticated from openedx.core.lib.api.view_utils import DeveloperErrorViewMixin from openedx.core.lib.exceptions import CourseNotFoundError @@ -168,7 +168,7 @@ class EnrollmentView(APIView, ApiKeyPermissionMixIn): authentication_classes = ( JwtAuthentication, - OAuth2AuthenticationAllowInactiveUser, + OAuth2AuthenticationAllowInactiveUserDeprecated, SessionAuthenticationAllowInactiveUser, ) permission_classes = (ApiKeyHeaderPermissionIsAuthenticated,) @@ -243,7 +243,7 @@ class EnrollmentUserRolesView(APIView): """ authentication_classes = ( JwtAuthentication, - OAuth2AuthenticationAllowInactiveUser, + OAuth2AuthenticationAllowInactiveUserDeprecated, EnrollmentCrossDomainSessionAuth, ) permission_classes = (ApiKeyHeaderPermissionIsAuthenticated,) @@ -612,7 +612,7 @@ class EnrollmentListView(APIView, ApiKeyPermissionMixIn): """ authentication_classes = ( JwtAuthentication, - OAuth2AuthenticationAllowInactiveUser, + OAuth2AuthenticationAllowInactiveUserDeprecated, EnrollmentCrossDomainSessionAuth, ) permission_classes = (ApiKeyHeaderPermissionIsAuthenticated,) @@ -940,7 +940,7 @@ class CourseEnrollmentsApiListView(DeveloperErrorViewMixin, ListAPIView): """ authentication_classes = ( JwtAuthentication, - OAuth2AuthenticationAllowInactiveUser, + OAuth2AuthenticationAllowInactiveUserDeprecated, SessionAuthenticationAllowInactiveUser, ) permission_classes = (permissions.IsAdminUser,) diff --git a/openedx/core/djangoapps/profile_images/views.py b/openedx/core/djangoapps/profile_images/views.py index b5a820a266..79fed3ea84 100644 --- a/openedx/core/djangoapps/profile_images/views.py +++ b/openedx/core/djangoapps/profile_images/views.py @@ -20,7 +20,7 @@ from six import text_type from openedx.core.djangoapps.user_api.accounts.image_helpers import get_profile_image_names, set_has_profile_image from openedx.core.djangoapps.user_api.errors import UserNotFound -from openedx.core.lib.api.authentication import OAuth2AuthenticationAllowInactiveUser +from openedx.core.lib.api.authentication import OAuth2AuthenticationAllowInactiveUserDeprecated from openedx.core.lib.api.parsers import TypedFileUploadParser from openedx.core.lib.api.permissions import IsUserInUrl from openedx.core.lib.api.view_utils import DeveloperErrorViewMixin @@ -115,7 +115,7 @@ class ProfileImageView(DeveloperErrorViewMixin, APIView): parser_classes = (MultiPartParser, FormParser, TypedFileUploadParser) authentication_classes = ( JwtAuthentication, - OAuth2AuthenticationAllowInactiveUser, + OAuth2AuthenticationAllowInactiveUserDeprecated, SessionAuthenticationAllowInactiveUser, ) permission_classes = (permissions.IsAuthenticated, IsUserInUrl) diff --git a/openedx/core/djangoapps/user_api/accounts/views.py b/openedx/core/djangoapps/user_api/accounts/views.py index 957ef0d9df..947dd807fe 100644 --- a/openedx/core/djangoapps/user_api/accounts/views.py +++ b/openedx/core/djangoapps/user_api/accounts/views.py @@ -49,7 +49,7 @@ from openedx.core.djangoapps.profile_images.images import remove_profile_images from openedx.core.djangoapps.user_api.accounts.image_helpers import get_profile_image_names, set_has_profile_image from openedx.core.djangoapps.user_authn.exceptions import AuthFailedError from openedx.core.djangolib.oauth2_retirement_utils import retire_dop_oauth2_models, retire_dot_oauth2_models -from openedx.core.lib.api.authentication import OAuth2AuthenticationAllowInactiveUser +from openedx.core.lib.api.authentication import OAuth2AuthenticationAllowInactiveUserDeprecated from openedx.core.lib.api.parsers import MergePatchParser from student.models import ( AccountRecovery, @@ -267,7 +267,7 @@ class AccountViewSet(ViewSet): If the update is successful, updated user account data is returned. """ authentication_classes = ( - JwtAuthentication, OAuth2AuthenticationAllowInactiveUser, SessionAuthenticationAllowInactiveUser + JwtAuthentication, OAuth2AuthenticationAllowInactiveUserDeprecated, SessionAuthenticationAllowInactiveUser ) permission_classes = (permissions.IsAuthenticated,) parser_classes = (MergePatchParser,) diff --git a/openedx/core/djangoapps/user_api/preferences/views.py b/openedx/core/djangoapps/user_api/preferences/views.py index f0b9b366a5..a3c82617d8 100644 --- a/openedx/core/djangoapps/user_api/preferences/views.py +++ b/openedx/core/djangoapps/user_api/preferences/views.py @@ -14,7 +14,7 @@ from rest_framework import permissions, status from rest_framework.response import Response from rest_framework.views import APIView -from openedx.core.lib.api.authentication import OAuth2AuthenticationAllowInactiveUser +from openedx.core.lib.api.authentication import OAuth2AuthenticationAllowInactiveUserDeprecated from openedx.core.lib.api.parsers import MergePatchParser from openedx.core.lib.api.permissions import IsUserInUrlOrStaff @@ -91,7 +91,7 @@ class PreferencesView(APIView): """ authentication_classes = ( JwtAuthentication, - OAuth2AuthenticationAllowInactiveUser, + OAuth2AuthenticationAllowInactiveUserDeprecated, SessionAuthenticationAllowInactiveUser, ) permission_classes = (permissions.IsAuthenticated, IsUserInUrlOrStaff) @@ -202,7 +202,7 @@ class PreferencesDetailView(APIView): If the update is successful, an HTTP 204 "No Content" response is returned with no additional content. """ - authentication_classes = (OAuth2AuthenticationAllowInactiveUser, SessionAuthenticationAllowInactiveUser) + authentication_classes = (OAuth2AuthenticationAllowInactiveUserDeprecated, SessionAuthenticationAllowInactiveUser) permission_classes = (permissions.IsAuthenticated, IsUserInUrlOrStaff) def get(self, request, username, preference_key): diff --git a/openedx/core/lib/api/authentication.py b/openedx/core/lib/api/authentication.py index 8ac1eb47f1..41955dae7a 100644 --- a/openedx/core/lib/api/authentication.py +++ b/openedx/core/lib/api/authentication.py @@ -1,6 +1,5 @@ """ Common Authentication Handlers used across projects. """ - import logging import django.utils.timezone @@ -35,6 +34,15 @@ class OAuth2AuthenticationDeprecated(OAuth2AuthenticationDeprecatedBase): fails. """ set_custom_metric("OAuth2AuthenticationDeprecated", "Failed") + try: + auth = get_authorization_header(request).split() + set_custom_metric('OAuth2Authentication_token_location', 'unknown') + + if auth and len(auth) == 2 and auth[0].lower() == b'bearer': + set_custom_metric('OAuth2Authentication_token_location', 'bearer-in-header') + except Exception: # pylint: disable=broad-except + pass + output = super(OAuth2AuthenticationDeprecated, self).authenticate(request) if output is None: set_custom_metric("OAuth2AuthenticationDeprecated", "None") @@ -43,7 +51,7 @@ class OAuth2AuthenticationDeprecated(OAuth2AuthenticationDeprecatedBase): return output -class OAuth2AuthenticationAllowInactiveUser(OAuth2AuthenticationDeprecated): +class OAuth2AuthenticationAllowInactiveUserDeprecated(OAuth2AuthenticationDeprecated): """ This is a temporary workaround while the is_active field on the user is coupled with whether or not the user has verified ownership of their claimed email address. @@ -65,7 +73,7 @@ class OAuth2AuthenticationAllowInactiveUser(OAuth2AuthenticationDeprecated): """ try: - return super(OAuth2AuthenticationAllowInactiveUser, self).authenticate(request) + return super(OAuth2AuthenticationAllowInactiveUserDeprecated, self).authenticate(request) except AuthenticationFailed as exc: if isinstance(exc.detail, dict): developer_message = exc.detail['developer_message'] @@ -135,6 +143,11 @@ class OAuth2Authentication(BaseAuthentication): www_authenticate_realm = 'api' + # currently, active users are users that confirm their email. + # a subclass could override `allow_inactive_users` to enable access without email confirmation, + # like in the case of mobile users. + allow_inactive_users = False + def authenticate(self, request): """ Returns tuple (user, token) if access token authentication succeeds, @@ -165,6 +178,8 @@ class OAuth2Authentication(BaseAuthentication): set_custom_metric("OAuth2Authentication", "None") return None + set_custom_metric("OAuth2Authentication_token_parts", len(access_token.split('.'))) + user, token = self.authenticate_credentials(access_token) set_custom_metric("OAuth2Authentication", "Success") @@ -199,8 +214,8 @@ class OAuth2Authentication(BaseAuthentication): }) else: user = token.user - # Check to make sure the users have activated their account(by confirming their email) - if not user.is_active: + # Check to make sure the users have activated their account (by confirming their email) + if not self.allow_inactive_users and not user.is_active: set_custom_metric("OAuth2Authentication_user_active", False) msg = 'User inactive or deleted: %s' % user.get_username() raise AuthenticationFailed({ @@ -251,3 +266,18 @@ class OAuth2Authentication(BaseAuthentication): header in a `401 Unauthenticated` response """ return 'Bearer realm="%s"' % self.www_authenticate_realm + + +class OAuth2AuthenticationAllowInactiveUser(OAuth2Authentication): + """ + Currently, is_active field on the user is coupled + with whether or not the user has verified ownership of their claimed email address. + Once is_active is decoupled from verified_email, we will no longer need this + class override. + + This class can be used for an OAuth2-accessible endpoint that allows users to access + that endpoint without having their email verified. For example, this is used + for mobile endpoints. + """ + + allow_inactive_users = True diff --git a/openedx/core/lib/api/tests/test_authentication.py b/openedx/core/lib/api/tests/test_authentication.py index dfbc9544bd..8afa8b6436 100644 --- a/openedx/core/lib/api/tests/test_authentication.py +++ b/openedx/core/lib/api/tests/test_authentication.py @@ -50,14 +50,18 @@ class MockView(APIView): # pylint: disable=missing-docstring # This is the a change we've made from the django-rest-framework-oauth version # of these tests. We're subclassing our custom OAuth2AuthenticationAllowInactiveUser # instead of OAuth2Authentication. -class OAuth2AuthenticationDebug(authentication.OAuth2AuthenticationAllowInactiveUser): +class OAuth2AuthenticationDebug(authentication.OAuth2AuthenticationAllowInactiveUserDeprecated): allow_query_params_token = True urlpatterns = [ url(r'^oauth2/', include(('provider.oauth2.urls', 'oauth2'), namespace='oauth2')), url( - r'^oauth2-deprecated-test/$', + r'^oauth2-inactive-deprecated-test/$', + MockView.as_view(authentication_classes=[authentication.OAuth2AuthenticationAllowInactiveUserDeprecated]) + ), + url( + r'^oauth2-inactive-test/$', MockView.as_view(authentication_classes=[authentication.OAuth2AuthenticationAllowInactiveUser]) ), url( @@ -69,7 +73,7 @@ urlpatterns = [ url( r'^oauth2-with-scope-test/$', MockView.as_view( - authentication_classes=[authentication.OAuth2AuthenticationAllowInactiveUser], + authentication_classes=[authentication.OAuth2AuthenticationAllowInactiveUserDeprecated], permission_classes=[permissions.TokenHasReadWriteScope] ) ), @@ -79,12 +83,12 @@ urlpatterns = [ @ddt.ddt # pylint: disable=missing-docstring @unittest.skipUnless(settings.FEATURES.get("ENABLE_OAUTH2_PROVIDER"), "OAuth2 not enabled") @override_settings(ROOT_URLCONF=__name__) -class OAuth2AllowInActiveUsersTests(TestCase): +class OAuth2AllowInActiveUsersDeprecatedTests(TestCase): - OAUTH2_BASE_TESTING_URL = '/oauth2-deprecated-test/' + OAUTH2_BASE_TESTING_URL = '/oauth2-inactive-deprecated-test/' def setUp(self): - super(OAuth2AllowInActiveUsersTests, self).setUp() + super(OAuth2AllowInActiveUsersDeprecatedTests, self).setUp() self.dop_adapter = adapters.DOPAdapter() self.dot_adapter = adapters.DOTAdapter() self.csrf_client = APIClient(enforce_csrf_checks=True) @@ -313,7 +317,7 @@ class OAuth2AllowInActiveUsersTests(TestCase): self.assertEqual(response.status_code, scope_statuses.write_status) -class OAuth2AuthenticationTests(OAuth2AllowInActiveUsersTests): # pylint: disable=test-inherits-tests +class OAuth2AuthenticationTests(OAuth2AllowInActiveUsersDeprecatedTests): # pylint: disable=test-inherits-tests OAUTH2_BASE_TESTING_URL = '/oauth2-test/' @@ -322,3 +326,8 @@ class OAuth2AuthenticationTests(OAuth2AllowInActiveUsersTests): # pylint: disab # Since this is testing back to previous version, user should be set to true self.user.is_active = True self.user.save() + + +class OAuth2AllowInActiveUsersTests(OAuth2AllowInActiveUsersDeprecatedTests): # pylint: disable=test-inherits-tests + + OAUTH2_BASE_TESTING_URL = '/oauth2-inactive-test/' diff --git a/openedx/core/lib/api/view_utils.py b/openedx/core/lib/api/view_utils.py index b25c072fc1..5879ab3aa2 100644 --- a/openedx/core/lib/api/view_utils.py +++ b/openedx/core/lib/api/view_utils.py @@ -23,7 +23,7 @@ from rest_framework.views import APIView from six import text_type, iteritems from openedx.core.djangoapps.content.course_overviews.models import CourseOverview -from openedx.core.lib.api.authentication import OAuth2AuthenticationAllowInactiveUser +from openedx.core.lib.api.authentication import OAuth2AuthenticationAllowInactiveUserDeprecated from openedx.core.lib.api.permissions import IsUserInUrl @@ -120,7 +120,7 @@ def view_auth_classes(is_user=False, is_authenticated=True): """ func_or_class.authentication_classes = ( JwtAuthentication, - OAuth2AuthenticationAllowInactiveUser, + OAuth2AuthenticationAllowInactiveUserDeprecated, SessionAuthenticationAllowInactiveUser ) func_or_class.permission_classes = () diff --git a/openedx/features/discounts/views.py b/openedx/features/discounts/views.py index 162e689e13..348ce8759e 100644 --- a/openedx/features/discounts/views.py +++ b/openedx/features/discounts/views.py @@ -18,7 +18,7 @@ from experiments.models import ExperimentData from openedx.core.djangoapps.content.course_overviews.models import CourseOverview from openedx.core.djangoapps.cors_csrf.decorators import ensure_csrf_cookie_cross_domain from openedx.core.djangoapps.oauth_dispatch.jwt import create_jwt_for_user -from openedx.core.lib.api.authentication import OAuth2AuthenticationAllowInactiveUser +from openedx.core.lib.api.authentication import OAuth2AuthenticationAllowInactiveUserDeprecated from openedx.core.lib.api.permissions import ApiKeyHeaderPermissionIsAuthenticated from openedx.core.lib.api.view_utils import DeveloperErrorViewMixin @@ -59,7 +59,7 @@ class CourseUserDiscount(DeveloperErrorViewMixin, APIView): "jwt": xxxxxxxx.xxxxxxxx.xxxxxxx } """ - authentication_classes = (JwtAuthentication, OAuth2AuthenticationAllowInactiveUser, + authentication_classes = (JwtAuthentication, OAuth2AuthenticationAllowInactiveUserDeprecated, SessionAuthenticationAllowInactiveUser,) permission_classes = (ApiKeyHeaderPermissionIsAuthenticated,) @@ -130,7 +130,7 @@ class CourseUserDiscountWithUserParam(DeveloperErrorViewMixin, APIView): "jwt": xxxxxxxx.xxxxxxxx.xxxxxxx } """ - authentication_classes = (JwtAuthentication, OAuth2AuthenticationAllowInactiveUser, + authentication_classes = (JwtAuthentication, OAuth2AuthenticationAllowInactiveUserDeprecated, SessionAuthenticationAllowInactiveUser,) permission_classes = (ApiKeyHeaderPermissionIsAuthenticated, IsAdminUser)