From 2968e3551b64fba983e13847253c3c30567efb9e Mon Sep 17 00:00:00 2001 From: Mushtaq Ali Date: Tue, 29 Nov 2016 16:48:30 +0500 Subject: [PATCH] Prevent non-video file formats - TNL-5956 --- .../contentstore/views/tests/test_videos.py | 70 +++++++- cms/djangoapps/contentstore/views/videos.py | 11 ++ cms/static/js/factories/videos_index.js | 4 +- .../views/active_video_upload_list_spec.js | 32 +++- .../js/views/active_video_upload_list.js | 157 +++++++++++++----- cms/templates/videos_index.html | 37 +++-- 6 files changed, 255 insertions(+), 56 deletions(-) diff --git a/cms/djangoapps/contentstore/views/tests/test_videos.py b/cms/djangoapps/contentstore/views/tests/test_videos.py index 3083f45708..6db155fbf8 100644 --- a/cms/djangoapps/contentstore/views/tests/test_videos.py +++ b/cms/djangoapps/contentstore/views/tests/test_videos.py @@ -3,6 +3,7 @@ Unit tests for video-related REST APIs. """ import csv +import ddt import json import dateutil.parser import re @@ -158,6 +159,7 @@ class VideoUploadTestMixin(object): self.assertEqual(self.client.get(self.url).status_code, 404) +@ddt.ddt @patch.dict("django.conf.settings.FEATURES", {"ENABLE_VIDEO_UPLOAD_PIPELINE": True}) @override_settings(VIDEO_UPLOAD_PIPELINE={"BUCKET": "test_bucket", "ROOT_PATH": "test_root"}) class VideosHandlerTestCase(VideoUploadTestMixin, CourseTestCase): @@ -220,6 +222,70 @@ class VideosHandlerTestCase(VideoUploadTestMixin, CourseTestCase): # Entry missing content_type assert_bad({"files": [{"file_name": "test.mp4"}]}) + @override_settings(AWS_ACCESS_KEY_ID="test_key_id", AWS_SECRET_ACCESS_KEY="test_secret") + @patch("boto.s3.key.Key") + @patch("boto.s3.connection.S3Connection") + @ddt.data( + ( + [ + { + "file_name": "supported-1.mp4", + "content_type": "video/mp4", + }, + { + "file_name": "supported-2.mov", + "content_type": "video/quicktime", + }, + ], + 200 + ), + ( + [ + { + "file_name": "unsupported-1.txt", + "content_type": "text/plain", + }, + { + "file_name": "unsupported-2.png", + "content_type": "image/png", + }, + ], + 400 + ) + ) + @ddt.unpack + def test_video_supported_file_formats(self, files, expected_status, mock_conn, mock_key): + """ + Test that video upload works correctly against supported and unsupported file formats. + """ + bucket = Mock() + mock_conn.return_value = Mock(get_bucket=Mock(return_value=bucket)) + mock_key_instances = [ + Mock( + generate_url=Mock( + return_value="http://example.com/url_{}".format(file_info["file_name"]) + ) + ) + for file_info in files + ] + # If extra calls are made, return a dummy + mock_key.side_effect = mock_key_instances + [Mock()] + + # Check supported formats + response = self.client.post( + self.url, + json.dumps({"files": files}), + content_type="application/json" + ) + self.assertEqual(response.status_code, expected_status) + response = json.loads(response.content) + + if expected_status == 200: + self.assertNotIn('error', response) + else: + self.assertIn('error', response) + self.assertEqual(response['error'], "Request 'files' entry contain unsupported content_type") + @override_settings(AWS_ACCESS_KEY_ID="test_key_id", AWS_SECRET_ACCESS_KEY="test_secret") @patch("boto.s3.key.Key") @patch("boto.s3.connection.S3Connection") @@ -230,8 +296,8 @@ class VideosHandlerTestCase(VideoUploadTestMixin, CourseTestCase): "content_type": "video/mp4", }, { - "file_name": "second.webm", - "content_type": "video/webm", + "file_name": "second.mp4", + "content_type": "video/mp4", }, { "file_name": "third.mov", diff --git a/cms/djangoapps/contentstore/views/videos.py b/cms/djangoapps/contentstore/views/videos.py index 3ef38bb87e..ada8081449 100644 --- a/cms/djangoapps/contentstore/views/videos.py +++ b/cms/djangoapps/contentstore/views/videos.py @@ -29,6 +29,11 @@ __all__ = ["videos_handler", "video_encodings_download"] # Default expiration, in seconds, of one-time URLs used for uploading videos. KEY_EXPIRATION_IN_SECONDS = 86400 +VIDEO_SUPPORTED_FILE_FORMATS = { + '.mp4': 'video/mp4', + '.mov': 'video/quicktime', +} + class StatusDisplayStrings(object): """ @@ -257,6 +262,7 @@ def videos_index_html(course): "encodings_download_url": reverse_course_url("video_encodings_download", unicode(course.id)), "previous_uploads": _get_index_videos(course), "concurrent_upload_limit": settings.VIDEO_UPLOAD_PIPELINE.get("CONCURRENT_UPLOAD_LIMIT", 0), + "video_supported_file_formats": VIDEO_SUPPORTED_FILE_FORMATS.keys() } ) @@ -305,6 +311,11 @@ def videos_post(course, request): for file in request.json["files"] ): error = "Request 'files' entry does not contain 'file_name' and 'content_type'" + elif any( + file['content_type'] not in VIDEO_SUPPORTED_FILE_FORMATS.values() + for file in request.json["files"] + ): + error = "Request 'files' entry contain unsupported content_type" if error: return JsonResponse({"error": error}, status=400) diff --git a/cms/static/js/factories/videos_index.js b/cms/static/js/factories/videos_index.js index 23749ddb76..47cd356912 100644 --- a/cms/static/js/factories/videos_index.js +++ b/cms/static/js/factories/videos_index.js @@ -9,12 +9,14 @@ define([ encodingsDownloadUrl, concurrentUploadLimit, uploadButton, - previousUploads + previousUploads, + videoSupportedFileFormats ) { var activeView = new ActiveVideoUploadListView({ postUrl: videoHandlerUrl, concurrentUploadLimit: concurrentUploadLimit, uploadButton: uploadButton, + videoSupportedFileFormats: videoSupportedFileFormats, onFileUploadDone: function(activeVideos) { $.ajax({ url: videoHandlerUrl, diff --git a/cms/static/js/spec/views/active_video_upload_list_spec.js b/cms/static/js/spec/views/active_video_upload_list_spec.js index fb08b88c4f..8e1ab82927 100644 --- a/cms/static/js/spec/views/active_video_upload_list_spec.js +++ b/cms/static/js/spec/views/active_video_upload_list_spec.js @@ -17,10 +17,12 @@ define( TemplateHelpers.installTemplate('active-video-upload-list'); this.postUrl = '/test/post/url'; this.uploadButton = $('