MA-291 Allow API access without email activation
This commit is contained in:
@@ -147,7 +147,7 @@ class EnrollmentTest(ModuleStoreTestCase, APITestCase):
|
||||
self.client.logout()
|
||||
|
||||
# Try to enroll, this should fail.
|
||||
self._create_enrollment(expected_status=status.HTTP_403_FORBIDDEN)
|
||||
self._create_enrollment(expected_status=status.HTTP_401_UNAUTHORIZED)
|
||||
|
||||
def test_user_not_activated(self):
|
||||
# Log out the default user, Bob.
|
||||
|
||||
@@ -9,7 +9,6 @@ from opaque_keys import InvalidKeyError
|
||||
from opaque_keys.edx.locator import CourseLocator
|
||||
from openedx.core.djangoapps.user_api import api as user_api
|
||||
from rest_framework import status
|
||||
from rest_framework.authentication import OAuth2Authentication
|
||||
from rest_framework import permissions
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.throttling import UserRateThrottle
|
||||
@@ -19,7 +18,7 @@ from opaque_keys import InvalidKeyError
|
||||
from enrollment import api
|
||||
from enrollment.errors import CourseNotFoundError, CourseEnrollmentError, CourseModeNotFoundError
|
||||
from embargo import api as embargo_api
|
||||
from util.authentication import SessionAuthenticationAllowInactiveUser
|
||||
from util.authentication import SessionAuthenticationAllowInactiveUser, OAuth2AuthenticationAllowInactiveUser
|
||||
from util.disable_rate_limit import can_disable_rate_limit
|
||||
|
||||
|
||||
@@ -71,7 +70,7 @@ class EnrollmentView(APIView):
|
||||
* user: The ID of the user.
|
||||
"""
|
||||
|
||||
authentication_classes = OAuth2Authentication, SessionAuthenticationAllowInactiveUser
|
||||
authentication_classes = OAuth2AuthenticationAllowInactiveUser, SessionAuthenticationAllowInactiveUser
|
||||
permission_classes = permissions.IsAuthenticated,
|
||||
throttle_classes = EnrollmentUserThrottle,
|
||||
|
||||
@@ -243,7 +242,7 @@ class EnrollmentListView(APIView):
|
||||
* user: The ID of the user.
|
||||
"""
|
||||
|
||||
authentication_classes = OAuth2Authentication, SessionAuthenticationAllowInactiveUser
|
||||
authentication_classes = OAuth2AuthenticationAllowInactiveUser, SessionAuthenticationAllowInactiveUser
|
||||
permission_classes = permissions.IsAuthenticated,
|
||||
throttle_classes = EnrollmentUserThrottle,
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
""" Common Authentication Handlers used across projects. """
|
||||
from rest_framework import authentication
|
||||
from rest_framework.exceptions import AuthenticationFailed
|
||||
from rest_framework.compat import oauth2_provider, provider_now
|
||||
|
||||
|
||||
class SessionAuthenticationAllowInactiveUser(authentication.SessionAuthentication):
|
||||
@@ -39,10 +41,42 @@ class SessionAuthenticationAllowInactiveUser(authentication.SessionAuthenticatio
|
||||
# Unauthenticated, CSRF validation not required
|
||||
# This is where regular `SessionAuthentication` checks that the user is active.
|
||||
# We have removed that check in this implementation.
|
||||
if not user:
|
||||
# But we added a check to prevent anonymous users since we require a logged-in account.
|
||||
if not user or user.is_anonymous():
|
||||
return None
|
||||
|
||||
self.enforce_csrf(request)
|
||||
|
||||
# CSRF passed with authenticated user
|
||||
return (user, None)
|
||||
|
||||
|
||||
class OAuth2AuthenticationAllowInactiveUser(authentication.OAuth2Authentication):
|
||||
"""
|
||||
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.
|
||||
Once is_active is decoupled from verified_email, we will no longer need this
|
||||
class override.
|
||||
|
||||
But until then, this authentication class ensures that the user is logged in,
|
||||
but does not require that their account "is_active".
|
||||
|
||||
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.
|
||||
|
||||
"""
|
||||
def authenticate_credentials(self, request, access_token):
|
||||
"""
|
||||
Authenticate the request, given the access token.
|
||||
Override base class implementation to discard failure if user is inactive.
|
||||
"""
|
||||
try:
|
||||
token = oauth2_provider.oauth2.models.AccessToken.objects.select_related('user')
|
||||
# provider_now switches to timezone aware datetime when
|
||||
# the oauth2_provider version supports to it.
|
||||
token = token.get(token=access_token, expires__gt=provider_now())
|
||||
except oauth2_provider.oauth2.models.AccessToken.DoesNotExist:
|
||||
raise AuthenticationFailed('Invalid token')
|
||||
|
||||
return token.user, token
|
||||
|
||||
63
common/djangoapps/util/tests/test_authentication.py
Normal file
63
common/djangoapps/util/tests/test_authentication.py
Normal file
@@ -0,0 +1,63 @@
|
||||
"""Tests for util.authentication module."""
|
||||
|
||||
from mock import patch
|
||||
from django.conf import settings
|
||||
from rest_framework import permissions
|
||||
from rest_framework.compat import patterns, url
|
||||
from rest_framework.tests import test_authentication
|
||||
from provider import scope, constants
|
||||
from unittest import skipUnless
|
||||
|
||||
from ..authentication import OAuth2AuthenticationAllowInactiveUser
|
||||
|
||||
|
||||
class OAuth2AuthAllowInactiveUserDebug(OAuth2AuthenticationAllowInactiveUser):
|
||||
"""
|
||||
A debug class analogous to the OAuth2AuthenticationDebug class that tests
|
||||
the OAuth2 flow with the access token sent in a query param."""
|
||||
allow_query_params_token = True
|
||||
|
||||
|
||||
# The following patch overrides the URL patterns for the MockView class used in
|
||||
# rest_framework.tests.test_authentication so that the corresponding AllowInactiveUser
|
||||
# classes are tested instead.
|
||||
@skipUnless(settings.FEATURES.get('ENABLE_OAUTH2_PROVIDER'), 'OAuth2 not enabled')
|
||||
@patch.object(
|
||||
test_authentication,
|
||||
'urlpatterns',
|
||||
patterns(
|
||||
'',
|
||||
url(
|
||||
r'^oauth2-test/$',
|
||||
test_authentication.MockView.as_view(authentication_classes=[OAuth2AuthenticationAllowInactiveUser])
|
||||
),
|
||||
url(
|
||||
r'^oauth2-test-debug/$',
|
||||
test_authentication.MockView.as_view(authentication_classes=[OAuth2AuthAllowInactiveUserDebug])
|
||||
),
|
||||
url(
|
||||
r'^oauth2-with-scope-test/$',
|
||||
test_authentication.MockView.as_view(
|
||||
authentication_classes=[OAuth2AuthenticationAllowInactiveUser],
|
||||
permission_classes=[permissions.TokenHasReadWriteScope]
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
class OAuth2AuthenticationAllowInactiveUserTestCase(test_authentication.OAuth2Tests):
|
||||
"""
|
||||
Tests the OAuth2AuthenticationAllowInactiveUser class by running all the existing tests in
|
||||
OAuth2Tests but with the is_active flag on the user set to False.
|
||||
"""
|
||||
def setUp(self):
|
||||
super(OAuth2AuthenticationAllowInactiveUserTestCase, self).setUp()
|
||||
|
||||
# set the user's is_active flag to False.
|
||||
self.user.is_active = False
|
||||
self.user.save()
|
||||
|
||||
# Override the SCOPE_NAME_DICT setting for tests for oauth2-with-scope-test. This is
|
||||
# needed to support READ and WRITE scopes as they currently aren't supported by the
|
||||
# edx-auth2-provider, and their scope values collide with other scopes defined in the
|
||||
# edx-auth2-provider.
|
||||
scope.SCOPE_NAME_DICT = {'read': constants.READ, 'write': constants.WRITE}
|
||||
Reference in New Issue
Block a user