Merge pull request #16489 from edx/revert-16479-revert-16201-transcript-secure-credentials
Revert "Revert "Transcript secure credentials""
This commit is contained in:
0
openedx/core/djangoapps/video_pipeline/__init__.py
Normal file
0
openedx/core/djangoapps/video_pipeline/__init__.py
Normal file
9
openedx/core/djangoapps/video_pipeline/admin.py
Normal file
9
openedx/core/djangoapps/video_pipeline/admin.py
Normal file
@@ -0,0 +1,9 @@
|
||||
"""
|
||||
Django admin for Video Pipeline models.
|
||||
"""
|
||||
from config_models.admin import ConfigurationModelAdmin
|
||||
from django.contrib import admin
|
||||
|
||||
from openedx.core.djangoapps.video_pipeline.models import VideoPipelineIntegration
|
||||
|
||||
admin.site.register(VideoPipelineIntegration, ConfigurationModelAdmin)
|
||||
51
openedx/core/djangoapps/video_pipeline/api.py
Normal file
51
openedx/core/djangoapps/video_pipeline/api.py
Normal file
@@ -0,0 +1,51 @@
|
||||
"""
|
||||
API utils in order to communicate to edx-video-pipeline.
|
||||
"""
|
||||
import json
|
||||
import logging
|
||||
|
||||
from django.core.exceptions import ObjectDoesNotExist
|
||||
from slumber.exceptions import HttpClientError
|
||||
|
||||
from openedx.core.djangoapps.video_pipeline.models import VideoPipelineIntegration
|
||||
from openedx.core.djangoapps.video_pipeline.utils import create_video_pipeline_api_client
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def update_3rd_party_transcription_service_credentials(**credentials_payload):
|
||||
"""
|
||||
Updates the 3rd party transcription service's credentials.
|
||||
|
||||
Arguments:
|
||||
credentials_payload(dict): A payload containing org, provider and its credentials.
|
||||
|
||||
Returns:
|
||||
A Boolean specifying whether the credentials were updated or not
|
||||
and an error response received from pipeline.
|
||||
"""
|
||||
error_response, is_updated = {}, False
|
||||
pipeline_integration = VideoPipelineIntegration.current()
|
||||
if pipeline_integration.enabled:
|
||||
try:
|
||||
video_pipeline_user = pipeline_integration.get_service_user()
|
||||
except ObjectDoesNotExist:
|
||||
return error_response, is_updated
|
||||
|
||||
client = create_video_pipeline_api_client(user=video_pipeline_user, api_url=pipeline_integration.api_url)
|
||||
|
||||
try:
|
||||
client.transcript_credentials.post(credentials_payload)
|
||||
is_updated = True
|
||||
except HttpClientError as ex:
|
||||
is_updated = False
|
||||
log.exception(
|
||||
('[video-pipeline-service] Unable to update transcript credentials '
|
||||
'-- org=%s -- provider=%s -- response=%s.'),
|
||||
credentials_payload.get('org'),
|
||||
credentials_payload.get('provider'),
|
||||
ex.content,
|
||||
)
|
||||
error_response = json.loads(ex.content)
|
||||
|
||||
return error_response, is_updated
|
||||
@@ -0,0 +1,31 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
from django.conf import settings
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='VideoPipelineIntegration',
|
||||
fields=[
|
||||
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
||||
('change_date', models.DateTimeField(auto_now_add=True, verbose_name='Change date')),
|
||||
('enabled', models.BooleanField(default=False, verbose_name='Enabled')),
|
||||
('api_url', models.URLField(help_text='edx-video-pipeline API URL.', verbose_name='Internal API URL')),
|
||||
('service_username', models.CharField(default=b'video_pipeline_service_user', help_text='Username created for Video Pipeline Integration, e.g. video_pipeline_service_user.', max_length=100)),
|
||||
('changed_by', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, editable=False, to=settings.AUTH_USER_MODEL, null=True, verbose_name='Changed by')),
|
||||
],
|
||||
options={
|
||||
'ordering': ('-change_date',),
|
||||
'abstract': False,
|
||||
},
|
||||
),
|
||||
]
|
||||
31
openedx/core/djangoapps/video_pipeline/models.py
Normal file
31
openedx/core/djangoapps/video_pipeline/models.py
Normal file
@@ -0,0 +1,31 @@
|
||||
"""
|
||||
Model to hold edx-video-pipeline configurations.
|
||||
"""
|
||||
from config_models.models import ConfigurationModel
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.db import models
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
|
||||
class VideoPipelineIntegration(ConfigurationModel):
|
||||
"""
|
||||
Manages configuration for connecting to the edx-video-pipeline service and using its API.
|
||||
"""
|
||||
api_url = models.URLField(
|
||||
verbose_name=_('Internal API URL'),
|
||||
help_text=_('edx-video-pipeline API URL.')
|
||||
)
|
||||
|
||||
service_username = models.CharField(
|
||||
max_length=100,
|
||||
default='video_pipeline_service_user',
|
||||
null=False,
|
||||
blank=False,
|
||||
help_text=_('Username created for Video Pipeline Integration, e.g. video_pipeline_service_user.')
|
||||
)
|
||||
|
||||
def get_service_user(self):
|
||||
# NOTE: We load the user model here to avoid issues at startup time that result from the hacks
|
||||
# in lms/startup.py.
|
||||
User = get_user_model() # pylint: disable=invalid-name
|
||||
return User.objects.get(username=self.service_username)
|
||||
23
openedx/core/djangoapps/video_pipeline/tests/mixins.py
Normal file
23
openedx/core/djangoapps/video_pipeline/tests/mixins.py
Normal file
@@ -0,0 +1,23 @@
|
||||
"""
|
||||
Mixins to test video pipeline integration.
|
||||
"""
|
||||
from openedx.core.djangoapps.video_pipeline.models import VideoPipelineIntegration
|
||||
|
||||
|
||||
class VideoPipelineIntegrationMixin(object):
|
||||
"""
|
||||
Utility for working with the video pipeline service during testing.
|
||||
"""
|
||||
video_pipeline_integration_defaults = {
|
||||
'enabled': True,
|
||||
'api_url': 'https://video-pipeline.example.com/api/v1/',
|
||||
'service_username': 'cms_video_pipeline_service_user',
|
||||
}
|
||||
|
||||
def create_video_pipeline_integration(self, **kwargs):
|
||||
"""
|
||||
Creates a new `VideoPipelineIntegration` record with `video_pipeline_integration_defaults`,
|
||||
and it can be updated with any provided overrides.
|
||||
"""
|
||||
fields = dict(self.video_pipeline_integration_defaults, **kwargs)
|
||||
return VideoPipelineIntegration.objects.create(**fields)
|
||||
99
openedx/core/djangoapps/video_pipeline/tests/test_api.py
Normal file
99
openedx/core/djangoapps/video_pipeline/tests/test_api.py
Normal file
@@ -0,0 +1,99 @@
|
||||
"""
|
||||
Tests for Video Pipeline api utils.
|
||||
"""
|
||||
import ddt
|
||||
import json
|
||||
from mock import Mock, patch
|
||||
|
||||
from django.test.testcases import TestCase
|
||||
from slumber.exceptions import HttpClientError
|
||||
|
||||
from student.tests.factories import UserFactory
|
||||
|
||||
from openedx.core.djangoapps.video_pipeline.api import update_3rd_party_transcription_service_credentials
|
||||
from openedx.core.djangoapps.video_pipeline.tests.mixins import VideoPipelineIntegrationMixin
|
||||
|
||||
|
||||
@ddt.ddt
|
||||
class TestAPIUtils(VideoPipelineIntegrationMixin, TestCase):
|
||||
"""
|
||||
Tests for API Utils.
|
||||
"""
|
||||
def setUp(self):
|
||||
self.pipeline_integration = self.create_video_pipeline_integration()
|
||||
self.user = UserFactory(username=self.pipeline_integration.service_username)
|
||||
|
||||
def test_update_transcription_service_credentials_with_integration_disabled(self):
|
||||
"""
|
||||
Test updating the credentials when service integration is disabled.
|
||||
"""
|
||||
self.pipeline_integration.enabled = False
|
||||
self.pipeline_integration.save()
|
||||
__, is_updated = update_3rd_party_transcription_service_credentials()
|
||||
self.assertFalse(is_updated)
|
||||
|
||||
def test_update_transcription_service_credentials_with_unknown_user(self):
|
||||
"""
|
||||
Test updating the credentials when expected service user is not registered.
|
||||
"""
|
||||
self.pipeline_integration.service_username = 'non_existent_user'
|
||||
self.pipeline_integration.save()
|
||||
__, is_updated = update_3rd_party_transcription_service_credentials()
|
||||
self.assertFalse(is_updated)
|
||||
|
||||
@ddt.data(
|
||||
{
|
||||
'username': 'Jason_cielo_24',
|
||||
'api_key': '12345678',
|
||||
},
|
||||
{
|
||||
'api_key': '12345678',
|
||||
'api_secret': '11111111',
|
||||
}
|
||||
)
|
||||
@patch('openedx.core.djangoapps.video_pipeline.api.log')
|
||||
@patch('openedx.core.djangoapps.video_pipeline.utils.EdxRestApiClient')
|
||||
def test_update_transcription_service_credentials(self, credentials_payload, mock_client, mock_logger):
|
||||
"""
|
||||
Tests that the update transcription service credentials api util works as expected.
|
||||
"""
|
||||
# Mock the post request
|
||||
mock_credentials_endpoint = mock_client.return_value.transcript_credentials
|
||||
# Try updating the transcription service credentials
|
||||
error_response, is_updated = update_3rd_party_transcription_service_credentials(**credentials_payload)
|
||||
|
||||
mock_credentials_endpoint.post.assert_called_with(credentials_payload)
|
||||
# Making sure log.exception is not called.
|
||||
self.assertDictEqual(error_response, {})
|
||||
self.assertFalse(mock_logger.exception.called)
|
||||
self.assertTrue(is_updated)
|
||||
|
||||
@patch('openedx.core.djangoapps.video_pipeline.api.log')
|
||||
@patch('openedx.core.djangoapps.video_pipeline.utils.EdxRestApiClient')
|
||||
def test_update_transcription_service_credentials_exceptions(self, mock_client, mock_logger):
|
||||
"""
|
||||
Tests that the update transcription service credentials logs the exception occurring
|
||||
during communication with edx-video-pipeline.
|
||||
"""
|
||||
error_content = '{"error_type": "1"}'
|
||||
# Mock the post request
|
||||
mock_credentials_endpoint = mock_client.return_value.transcript_credentials
|
||||
mock_credentials_endpoint.post = Mock(side_effect=HttpClientError(content=error_content))
|
||||
# try updating the transcription service credentials
|
||||
credentials_payload = {
|
||||
'org': 'mit',
|
||||
'provider': 'ABC Provider',
|
||||
'api_key': '61c56a8d0'
|
||||
}
|
||||
error_response, is_updated = update_3rd_party_transcription_service_credentials(**credentials_payload)
|
||||
|
||||
mock_credentials_endpoint.post.assert_called_with(credentials_payload)
|
||||
# Assert the results.
|
||||
self.assertFalse(is_updated)
|
||||
self.assertDictEqual(error_response, json.loads(error_content))
|
||||
mock_logger.exception.assert_called_with(
|
||||
'[video-pipeline-service] Unable to update transcript credentials -- org=%s -- provider=%s -- response=%s.',
|
||||
credentials_payload['org'],
|
||||
credentials_payload['provider'],
|
||||
error_content
|
||||
)
|
||||
19
openedx/core/djangoapps/video_pipeline/utils.py
Normal file
19
openedx/core/djangoapps/video_pipeline/utils.py
Normal file
@@ -0,0 +1,19 @@
|
||||
from django.conf import settings
|
||||
from edx_rest_api_client.client import EdxRestApiClient
|
||||
|
||||
from openedx.core.lib.token_utils import JwtBuilder
|
||||
|
||||
|
||||
def create_video_pipeline_api_client(user, api_url):
|
||||
"""
|
||||
Returns an API client which can be used to make Video Pipeline API requests.
|
||||
|
||||
Arguments:
|
||||
user(User): A requesting user.
|
||||
api_url(unicode): It is video pipeline's API URL.
|
||||
"""
|
||||
jwt_token = JwtBuilder(user).build_token(
|
||||
scopes=[],
|
||||
expires_in=settings.OAUTH_ID_TOKEN_EXPIRATION
|
||||
)
|
||||
return EdxRestApiClient(api_url, jwt=jwt_token)
|
||||
Reference in New Issue
Block a user