Commit 7c703719 by Mushtaq Ali Committed by GitHub

Merge pull request #45 from edx/mushtaq/save-credentials-backend-view

Save transcript credentials backend view
parents 62910040 77964646
......@@ -31,6 +31,12 @@ urlpatterns = [
url(r'^accounts/logout/$', 'django.contrib.auth.views.logout'),
url(r'^o/', include('oauth2_provider.urls', namespace='oauth2_provider')),
url(r'^api/', include(router.urls)),
# Transcript credentials handler view
url(
regex=r'^api/transcript_credentials/$',
view=views.TranscriptCredentialsView.as_view(),
name='transcript_credentials'
),
url(r'^api-auth/', include('rest_framework.urls', namespace='rest_framework')),
# Cheap auth server
url(r'^veda_auth/', views.token_auth),
......
......@@ -238,7 +238,11 @@ class Cielo24TranscriptTests(APITestCase):
# verify requests
self.assertTrue(
responses.calls[0].request.url,
'http://api.cielo24.com/job/get_caption?api_token=i_am_key&job_id=%28100%2C%29&caption_format=SRT&v=1'
('http://api.cielo24.com/job/get_caption'
'?api_token=i_am_key&job_id=%28100%2C%29&caption_format=SRT&v={cielo_api_version}'
).format(
cielo_api_version=transcripts.CIELO24_API_VERSION
)
)
self.assertEqual(responses.calls[2].request.url, CONFIG_DATA['val_transcript_create_url'])
......
""" Views tests """
import json
import responses
from ddt import data, ddt, unpack
from django.contrib.auth.models import User
from django.core.urlresolvers import reverse
from mock import patch
from rest_framework import status
from rest_framework.test import APITestCase
from VEDA_OS01.models import TranscriptCredentials, TranscriptProvider
from VEDA_OS01.views import CIELO24_LOGIN_URL
@ddt
class TranscriptCredentialsTest(APITestCase):
"""
Transcript credentials tests
"""
def setUp(self):
"""
Tests setup.
"""
super(TranscriptCredentialsTest, self).setUp()
self.url = reverse('transcript_credentials')
self.user = User.objects.create_user('test_user', 'test@user.com', 'test')
self.client.login(username=self.user.username, password='test')
def test_transcript_credentials_get_not_allowed(self):
"""
Tests that GET method is not allowed.
"""
response = self.client.get(self.url)
self.assertEqual(response.status_code, status.HTTP_405_METHOD_NOT_ALLOWED)
def test_transcript_credentials_unauthorized(self):
"""
Tests that if user is not logged in we get Unauthorized response.
"""
# Logout client if previously logged in.
self.client.logout()
# Try to send post without being authorized / logged in.
response = self.client.post(
self.url,
data=json.dumps({'org': 'test'}),
content_type='application/json'
)
response_status_code = response.status_code
response = json.loads(response.content)
self.assertEqual(response_status_code, status.HTTP_401_UNAUTHORIZED)
@data(
{},
{
'provider': 'unsupported-provider'
},
{
'org': 'test',
'api_key': 'test-api-key'
}
)
def test_transcript_credentials_invalid_provider(self, post_data):
"""
Test that post crednetials gives proper error in case of invalid provider.
"""
# Verify that transcript credentials are not present for this org and provider.
provider = post_data.get('provider')
response = self.client.post(
self.url,
data=json.dumps(post_data),
content_type='application/json'
)
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
response = json.loads(response.content)
self.assertEqual(response['message'], 'Invalid provider {provider}.'.format(provider=provider))
@data(
(
{
'provider': TranscriptProvider.CIELO24
},
'org and api_key and username'
),
(
{
'provider': TranscriptProvider.THREE_PLAY
},
'org and api_key and api_secret_key'
),
(
{
'provider': TranscriptProvider.CIELO24,
'org': 'test-org'
},
'api_key and username'
),
(
{
'provider': TranscriptProvider.CIELO24,
'org': 'test-org',
'api_key': 'test-api-key'
},
'username'
),
(
{
'org': 'test',
'provider': TranscriptProvider.THREE_PLAY,
'api_key': 'test-api-key'
},
'api_secret_key'
)
)
@unpack
def test_transcript_credentials_error(self, post_data, missing_keys):
"""
Test that post credentials gives proper error in case of invalid input.
"""
provider = post_data.get('provider')
error_message = '{missing} must be specified for {provider}.'.format(
provider=provider,
missing=missing_keys
)
response = self.client.post(
self.url,
data=json.dumps(post_data),
content_type='application/json'
)
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
response = json.loads(response.content)
self.assertEqual(response['message'], error_message)
@data(
{
'org': 'test',
'provider': TranscriptProvider.CIELO24,
'api_key': 'test-api-key',
'username': 'test-cielo-user'
},
{
'org': 'test',
'provider': TranscriptProvider.THREE_PLAY,
'api_key': 'test-api-key',
'api_secret_key': 'test-secret-key'
}
)
@responses.activate
def test_transcript_credentials_success(self, post_data):
"""
Test that post credentials works as expected.
"""
# Mock get_cielo_token_mock to return token
responses.add(
responses.GET,
CIELO24_LOGIN_URL,
body='{"ApiToken": "cielo-api-token"}',
status=status.HTTP_200_OK
)
# Verify that transcript credentials are not present for this org and provider.
transcript_credentials = TranscriptCredentials.objects.filter(
org=post_data.get('org'),
provider=post_data.get('provider')
)
self.assertFalse(transcript_credentials.exists())
response = self.client.post(self.url, data=json.dumps(post_data), content_type='application/json')
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
transcript_credentials = TranscriptCredentials.objects.filter(
org=post_data.get('org'),
provider=post_data.get('provider')
)
self.assertTrue(transcript_credentials.exists())
@patch('VEDA_OS01.views.LOGGER')
@responses.activate
def test_cielo24_error(self, mock_logger):
"""
Test that when invalid cielo credentials are supplied, we get correct response.
"""
# Mock get_cielo_token_response.
error_message = 'Invalid credentials supplied.'
responses.add(
responses.GET,
CIELO24_LOGIN_URL,
body=json.dumps({'error': error_message}),
status=status.HTTP_400_BAD_REQUEST
)
post_data = {
'org': 'test',
'provider': TranscriptProvider.CIELO24,
'api_key': 'test-api-key',
'username': 'test-cielo-user',
'api_secret_key': ''
}
response = self.client.post(
self.url,
data=json.dumps(post_data),
content_type='application/json'
)
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
response = json.loads(response.content)
self.assertEqual(response['message'], error_message)
mock_logger.warning.assert_called_with(
'[Transcript Credentials] Unable to get api token -- response %s -- status %s.',
json.dumps({'error': error_message}),
status.HTTP_400_BAD_REQUEST
)
......@@ -39,6 +39,9 @@ CIELO24_TRANSCRIPT_COMPLETED = django.dispatch.Signal(providing_args=[
])
CONFIG = get_config()
# Cielo24 API version
CIELO24_API_VERSION = 1
# Cielo24 API URLs
CIELO24_GET_CAPTION_URL = build_url(
CONFIG['cielo24_api_base_url'],
......@@ -210,7 +213,7 @@ def cielo24_transcript_callback(sender, **kwargs):
try:
srt_data = fetch_srt_data(
CIELO24_GET_CAPTION_URL,
v=1,
v=CIELO24_API_VERSION,
job_id=job_id,
api_token=api_key,
caption_format='SRT'
......
"""views"""
import json
import logging
import requests
from django.http import HttpResponse
from django.http import HttpResponseRedirect
from django.views.decorators.csrf import csrf_exempt
from rest_framework import renderers
from rest_framework import viewsets
from rest_framework import filters, renderers, status, viewsets
from rest_framework.decorators import detail_route
from rest_framework import filters
from rest_framework.response import Response
from rest_framework.views import APIView
from api import token_finisher
from VEDA_OS01.models import Course, Video, URL, Encode
from VEDA_OS01.serializers import CourseSerializer
from VEDA_OS01.serializers import VideoSerializer
from VEDA_OS01.serializers import EncodeSerializer
from VEDA_OS01.serializers import URLSerializer
from VEDA import utils
from VEDA_OS01.models import Course, Video, URL, Encode, TranscriptCredentials, TranscriptProvider
from VEDA_OS01.serializers import CourseSerializer, EncodeSerializer, VideoSerializer, URLSerializer
from VEDA_OS01.transcripts import CIELO24_API_VERSION
LOGGER = logging.getLogger(__name__)
CONFIG = utils.get_config()
CIELO24_LOGIN_URL = utils.build_url(
CONFIG['cielo24_api_base_url'],
'/account/login'
)
class CourseViewSet(viewsets.ModelViewSet):
queryset = Course.objects.all()
......@@ -96,6 +108,156 @@ class URLViewSet(viewsets.ModelViewSet):
serializer.save()
class TranscriptCredentialsView(APIView):
"""
A Transcript credentials View, used by platform to create/update transcript credentials.
"""
def get_cielo_token_response(self, username, api_secure_key):
"""
Returns Cielo24 api token.
Arguments:
username(str): Cielo24 username
api_securekey(str): Cielo24 api key
Returns:
Response : Http response object
"""
return requests.get(CIELO24_LOGIN_URL, params={
'v': CIELO24_API_VERSION,
'username': username,
'securekey': api_secure_key
})
def get_api_token(self, username, api_key):
"""
Returns api token if valid credentials are provided.
"""
response = self.get_cielo_token_response(username=username, api_secure_key=api_key)
if not response.ok:
api_token = None
LOGGER.warning(
'[Transcript Credentials] Unable to get api token -- response %s -- status %s.',
response.text,
response.status_code,
)
else:
api_token = json.loads(response.content)['ApiToken']
return api_token
def validate_missing_attributes(self, provider, attributes, credentials):
"""
Returns error message if provided attributes are not presents in credentials.
"""
error_message = None
missing = [attr for attr in attributes if attr not in credentials]
if missing:
error_message = u'{missing} must be specified for {provider}.'.format(
provider=provider,
missing=' and '.join(missing)
)
return error_message
def validate_transcript_credentials(self, provider, **credentials):
"""
Validates transcript credentials.
Validations:
Providers must be either 3PlayMedia or Cielo24.
In case of:
3PlayMedia - 'api_key' and 'api_secret_key' are required.
Cielo24 - Valid 'api_key' and 'username' are required.
"""
error_message, validated_credentials = '', {}
if provider in [TranscriptProvider.CIELO24, TranscriptProvider.THREE_PLAY]:
if provider == TranscriptProvider.CIELO24:
must_have_props = ('org', 'api_key', 'username')
error_message = self.validate_missing_attributes(provider, must_have_props, credentials)
if not error_message:
# Get cielo api token and store it in api_key.
api_token = self.get_api_token(credentials['username'], credentials['api_key'])
if api_token:
validated_credentials.update({
'org': credentials['org'],
'api_key': api_token
})
else:
error_message = u'Invalid credentials supplied.'
else:
must_have_props = ('org', 'api_key', 'api_secret_key')
error_message = self.validate_missing_attributes(provider, must_have_props, credentials)
if not error_message:
validated_credentials.update({
'org': credentials['org'],
'api_key': credentials['api_key'],
'api_secret': credentials['api_secret_key']
})
else:
error_message = u'Invalid provider {provider}.'.format(provider=provider)
return error_message, validated_credentials
def post(self, request):
"""
Creates or updates the org-specific transcript credentials with the given information.
Arguments:
request: A WSGI request.
**Example Request**
POST /api/transcript_credentials {
"provider": "3PlayMedia",
"org": "test.x",
"api_key": "test-api-key",
"api_secret_key": "test-api-secret-key"
}
**POST Parameters**
A POST request can include the following parameters.
* provider: A string representation of provider.
* org: A string representing the organizaton code.
* api_key: A string representing the provider api key.
* api_secret_key: (Required for 3Play only). A string representing the api secret key.
* username: (Required for Cielo only). A string representing the cielo username.
**Example POST Response**
In case of success:
Returns an empty response with 201 status code (HTTP 201 Created).
In case of error:
Return response with error message and 400 status code (HTTP 400 Bad Request).
{
"message": "Error message."
}
"""
# Validate credentials
provider = request.data.pop('provider', None)
error_message, validated_credentials = self.validate_transcript_credentials(provider=provider, **request.data)
if error_message:
return Response(
status=status.HTTP_400_BAD_REQUEST,
data=dict(message=error_message)
)
TranscriptCredentials.objects.update_or_create(
org=validated_credentials.pop('org'), provider=provider, defaults=validated_credentials
)
return Response(status=status.HTTP_201_CREATED)
@csrf_exempt
def token_auth(request):
"""
......
......@@ -12,6 +12,7 @@ from VEDA_OS01.models import (Cielo24Fidelity, Cielo24Turnaround, Course,
TranscriptProcessMetadata, TranscriptStatus,
Video)
from VEDA.utils import build_url
from VEDA_OS01.transcripts import CIELO24_API_VERSION
VIDEO_DATA = {
'studio_id': '12345',
......@@ -120,7 +121,7 @@ class Cielo24TranscriptTests(TestCase):
{
'url': build_url(
'https://sandbox.cielo24.com/api/job/new',
v=1,
v=CIELO24_API_VERSION,
job_name='12345',
language='en', # A job's language.
api_token='cielo24_api_key',
......@@ -131,7 +132,7 @@ class Cielo24TranscriptTests(TestCase):
{
'url': build_url(
'https://sandbox.cielo24.com/api/job/add_media',
v=1,
v=CIELO24_API_VERSION,
job_id='000-111-222',
api_token='cielo24_api_key',
media_url='https://s3.amazonaws.com/bkt/video.mp4',
......@@ -142,7 +143,7 @@ class Cielo24TranscriptTests(TestCase):
{
'url': build_url(
'https://sandbox.cielo24.com/api/job/perform_transcription',
v=1,
v=CIELO24_API_VERSION,
job_id='000-111-222',
target_language='TARGET_LANG',
callback_url=build_url(
......
......@@ -11,6 +11,7 @@ from requests.packages.urllib3.exceptions import InsecurePlatformWarning
from VEDA_OS01.models import (TranscriptProcessMetadata, TranscriptProvider,
TranscriptStatus)
from VEDA.utils import build_url
from VEDA_OS01.transcripts import CIELO24_API_VERSION
requests.packages.urllib3.disable_warnings(InsecurePlatformWarning)
......@@ -129,7 +130,7 @@ class Cielo24Transcript(object):
build_url(
self.cielo24_api_base_url,
self.cielo24_perform_transcription,
v=1,
v=CIELO24_API_VERSION,
job_id=job_id,
target_language=lang_code,
callback_url=callback_url,
......@@ -171,7 +172,7 @@ class Cielo24Transcript(object):
build_url(
self.cielo24_api_base_url,
self.cielo24_add_media,
v=1,
v=CIELO24_API_VERSION,
job_id=job_id,
api_token=self.api_key,
media_url=self.s3_video_url
......@@ -205,7 +206,7 @@ class Cielo24Transcript(object):
create_job_url = build_url(
self.cielo24_api_base_url,
self.cielo24_new_job,
v=1,
v=CIELO24_API_VERSION,
language=self.video.source_language,
api_token=self.api_key,
job_name=self.video.studio_id
......
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