Configure LMS to select oauth2 providing library.
Available backends: * django-oauth-toolkit (DOT) * django-oauth2-provider (DOP) * Use provided client ID to select backend for * AccessToken requests * third party auth-token exchange * Create adapters to isolate library-dependent functionality * Handle django-oauth-toolkit tokens in edX DRF authenticator class MA-1998 MA-2000
This commit is contained in:
@@ -12,8 +12,8 @@ from celery.exceptions import MaxRetriesExceededError
|
||||
from django.conf import settings
|
||||
from django.test import override_settings, TestCase
|
||||
from edx_rest_api_client.client import EdxRestApiClient
|
||||
|
||||
from edx_oauth2_provider.tests.factories import ClientFactory
|
||||
|
||||
from openedx.core.djangoapps.credentials.tests.mixins import CredentialsApiConfigMixin
|
||||
from openedx.core.djangoapps.programs.tests.mixins import ProgramsApiConfigMixin
|
||||
from openedx.core.djangoapps.programs.tasks.v1 import tasks
|
||||
|
||||
@@ -2,10 +2,13 @@
|
||||
|
||||
import logging
|
||||
|
||||
import django.utils.timezone
|
||||
from rest_framework.authentication import SessionAuthentication
|
||||
from rest_framework import exceptions as drf_exceptions
|
||||
from rest_framework_oauth.authentication import OAuth2Authentication
|
||||
from rest_framework_oauth.compat import oauth2_provider, provider_now
|
||||
|
||||
from provider.oauth2 import models as dop_models
|
||||
from oauth2_provider import models as dot_models
|
||||
|
||||
from openedx.core.lib.api.exceptions import AuthenticationFailed
|
||||
|
||||
@@ -114,21 +117,44 @@ class OAuth2AuthenticationAllowInactiveUser(OAuth2Authentication):
|
||||
def authenticate_credentials(self, request, access_token):
|
||||
"""
|
||||
Authenticate the request, given the access token.
|
||||
Overrides base class implementation to discard failure if user is inactive.
|
||||
|
||||
Overrides base class implementation to discard failure if user is
|
||||
inactive.
|
||||
"""
|
||||
token_query = oauth2_provider.oauth2.models.AccessToken.objects.select_related('user')
|
||||
token = token_query.filter(token=access_token).first()
|
||||
|
||||
token = self.get_access_token(access_token)
|
||||
if not token:
|
||||
raise AuthenticationFailed({
|
||||
u'error_code': OAUTH2_TOKEN_ERROR_NONEXISTENT,
|
||||
u'developer_message': u'The provided access token does not match any valid tokens.'
|
||||
})
|
||||
# provider_now switches to timezone aware datetime when
|
||||
# the oauth2_provider version supports it.
|
||||
elif token.expires < provider_now():
|
||||
elif token.expires < django.utils.timezone.now():
|
||||
raise AuthenticationFailed({
|
||||
u'error_code': OAUTH2_TOKEN_ERROR_EXPIRED,
|
||||
u'developer_message': u'The provided access token has expired and is no longer valid.',
|
||||
})
|
||||
else:
|
||||
return token.user, token
|
||||
|
||||
def get_access_token(self, access_token):
|
||||
"""
|
||||
Return a valid access token that exists in one of our OAuth2 libraries,
|
||||
or None if no matching token is found.
|
||||
"""
|
||||
return self._get_dot_token(access_token) or self._get_dop_token(access_token)
|
||||
|
||||
def _get_dop_token(self, access_token):
|
||||
"""
|
||||
Return a valid access token stored by django-oauth2-provider (DOP), or
|
||||
None if no matching token is found.
|
||||
"""
|
||||
token_query = dop_models.AccessToken.objects.select_related('user')
|
||||
return token_query.filter(token=access_token).first()
|
||||
|
||||
def _get_dot_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')
|
||||
return token_query.filter(token=access_token).first()
|
||||
|
||||
@@ -19,6 +19,7 @@ from django.utils import unittest
|
||||
from django.utils.http import urlencode
|
||||
from mock import patch
|
||||
from nose.plugins.attrib import attr
|
||||
from oauth2_provider import models as dot_models
|
||||
from rest_framework import exceptions
|
||||
from rest_framework import status
|
||||
from rest_framework.permissions import IsAuthenticated
|
||||
@@ -28,6 +29,7 @@ from rest_framework.test import APIRequestFactory, APIClient
|
||||
from rest_framework.views import APIView
|
||||
from rest_framework_jwt.settings import api_settings
|
||||
|
||||
from lms.djangoapps.oauth_dispatch import adapters
|
||||
from openedx.core.lib.api import authentication
|
||||
from openedx.core.lib.api.tests.mixins import JwtMixin
|
||||
from provider import constants, scope
|
||||
@@ -84,6 +86,8 @@ class OAuth2Tests(TestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(OAuth2Tests, self).setUp()
|
||||
self.dop_adapter = adapters.DOPAdapter()
|
||||
self.dot_adapter = adapters.DOTAdapter()
|
||||
self.csrf_client = APIClient(enforce_csrf_checks=True)
|
||||
self.username = 'john'
|
||||
self.email = 'lennon@thebeatles.com'
|
||||
@@ -95,24 +99,35 @@ class OAuth2Tests(TestCase):
|
||||
self.ACCESS_TOKEN = 'access_token' # pylint: disable=invalid-name
|
||||
self.REFRESH_TOKEN = 'refresh_token' # pylint: disable=invalid-name
|
||||
|
||||
self.oauth2_client = oauth2_provider.oauth2.models.Client.objects.create(
|
||||
client_id=self.CLIENT_ID,
|
||||
client_secret=self.CLIENT_SECRET,
|
||||
redirect_uri='',
|
||||
client_type=0,
|
||||
self.dop_oauth2_client = self.dop_adapter.create_public_client(
|
||||
name='example',
|
||||
user=None,
|
||||
user=self.user,
|
||||
client_id=self.CLIENT_ID,
|
||||
redirect_uri='https://example.edx/redirect',
|
||||
)
|
||||
|
||||
self.access_token = oauth2_provider.oauth2.models.AccessToken.objects.create(
|
||||
token=self.ACCESS_TOKEN,
|
||||
client=self.oauth2_client,
|
||||
client=self.dop_oauth2_client,
|
||||
user=self.user,
|
||||
)
|
||||
self.refresh_token = oauth2_provider.oauth2.models.RefreshToken.objects.create(
|
||||
user=self.user,
|
||||
access_token=self.access_token,
|
||||
client=self.oauth2_client
|
||||
client=self.dop_oauth2_client,
|
||||
)
|
||||
|
||||
self.dot_oauth2_client = self.dot_adapter.create_public_client(
|
||||
name='example',
|
||||
user=self.user,
|
||||
client_id='dot-client-id',
|
||||
redirect_uri='https://example.edx/redirect',
|
||||
)
|
||||
self.dot_access_token = dot_models.AccessToken.objects.create(
|
||||
user=self.user,
|
||||
token='dot-access-token',
|
||||
application=self.dot_oauth2_client,
|
||||
expires=datetime.now() + timedelta(days=30),
|
||||
)
|
||||
|
||||
# This is the a change we've made from the django-rest-framework-oauth version
|
||||
@@ -182,6 +197,10 @@ class OAuth2Tests(TestCase):
|
||||
response = self.get_with_bearer_token('/oauth2-test/')
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
|
||||
def test_get_form_passing_auth_with_dot(self):
|
||||
response = self.get_with_bearer_token('/oauth2-test/', token=self.dot_access_token.token)
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
|
||||
@unittest.skipUnless(oauth2_provider, 'django-oauth2-provider not installed')
|
||||
def test_post_form_passing_auth_url_transport(self):
|
||||
"""Ensure GETing form over OAuth with correct client credentials in form data succeed"""
|
||||
|
||||
Reference in New Issue
Block a user