Commit 83a3adb9 by Nimisha Asthagiri

MA-291 Allow API access without email activation

parent 18a916ba
......@@ -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,
......
""" 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
"""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}
......@@ -4,10 +4,9 @@ Common utility methods and decorators for Mobile APIs.
import functools
from rest_framework import permissions
from rest_framework.authentication import OAuth2Authentication, SessionAuthentication
from util.authentication import SessionAuthenticationAllowInactiveUser, OAuth2AuthenticationAllowInactiveUser
from opaque_keys.edx.keys import CourseKey
from xmodule.modulestore.django import modulestore
from courseware.courses import get_course_with_access
......@@ -55,7 +54,10 @@ def mobile_view(is_user=False):
Requires either OAuth2 or Session-based authentication.
If is_user is True, also requires username in URL matches the request user.
"""
func_or_class.authentication_classes = (OAuth2Authentication, SessionAuthentication)
func_or_class.authentication_classes = (
OAuth2AuthenticationAllowInactiveUser,
SessionAuthenticationAllowInactiveUser
)
func_or_class.permission_classes = (permissions.IsAuthenticated,)
if is_user:
func_or_class.permission_classes += (IsUser,)
......
......@@ -33,7 +33,7 @@ git+https://github.com/mitocw/django-cas.git@60a5b8e5a62e63e0d5d224a87f0b489201a
-e git+https://github.com/edx/opaque-keys.git@1254ed4d615a428591850656f39f26509b86d30a#egg=opaque-keys
-e git+https://github.com/edx/ease.git@97de68448e5495385ba043d3091f570a699d5b5f#egg=ease
-e git+https://github.com/edx/i18n-tools.git@193cebd9aa784f8899ef496f2aa050b08eff402b#egg=i18n-tools
-e git+https://github.com/edx/edx-oauth2-provider.git@0.4.0#egg=oauth2-provider
-e git+https://github.com/edx/edx-oauth2-provider.git@0.4.1#egg=oauth2-provider
-e git+https://github.com/edx/edx-val.git@9ceddb4944d0a1264b345947bf486340c5774a00#egg=edx-val
-e git+https://github.com/pmitros/RecommenderXBlock.git@9b07e807c89ba5761827d0387177f71aa57ef056#egg=recommender-xblock
-e git+https://github.com/edx/edx-milestones.git@547f2250ee49e73ce8d7ff4e78ecf1b049892510#egg=edx-milestones
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment