From e635ba840d623eb89d4b0db174ea5c08dadfeb6e Mon Sep 17 00:00:00 2001 From: Laurent David Date: Wed, 17 Aug 2022 19:54:36 +0200 Subject: [PATCH] MDL-74664 mod_bigbluebuttonbn: Restricted recording access fix * Recording formats should not be accessible to students unless specified as safe * A global setting will now list all the "safe" formats for students * A new capability 'mod/bigbluebuttonbn:viewallrecordingsformats' will allow users to see all formats (as long as they can manage recordings also) --- .../classes/output/recording_row_playback.php | 25 ++- mod/bigbluebuttonbn/classes/settings.php | 20 +++ mod/bigbluebuttonbn/db/access.php | 11 ++ .../lang/en/bigbluebuttonbn.php | 6 + mod/bigbluebuttonbn/tests/generator/lib.php | 4 +- .../output/recording_row_playback_test.php | 151 ++++++++++++++++++ mod/bigbluebuttonbn/version.php | 2 +- 7 files changed, 203 insertions(+), 16 deletions(-) create mode 100644 mod/bigbluebuttonbn/tests/output/recording_row_playback_test.php diff --git a/mod/bigbluebuttonbn/classes/output/recording_row_playback.php b/mod/bigbluebuttonbn/classes/output/recording_row_playback.php index fed703f297f..f544251724e 100644 --- a/mod/bigbluebuttonbn/classes/output/recording_row_playback.php +++ b/mod/bigbluebuttonbn/classes/output/recording_row_playback.php @@ -18,6 +18,7 @@ namespace mod_bigbluebuttonbn\output; use mod_bigbluebuttonbn\instance; use mod_bigbluebuttonbn\local\bigbluebutton\recordings\recording_data; +use mod_bigbluebuttonbn\local\config; use mod_bigbluebuttonbn\local\helpers\roles; use mod_bigbluebuttonbn\recording; use renderable; @@ -106,25 +107,21 @@ class recording_row_playback implements renderable, templatable { if (array_key_exists('restricted', $playback) && strtolower($playback['restricted']) == 'false') { return true; } - // All types that are not statistics are included. - if ($playback['type'] != 'statistics') { - return true; - } // Exclude imported recordings. if ($this->recording->get('imported')) { return false; } - - // Exclude non moderators. - if ($this->instance) { - if (!$this->instance->is_admin() && !$this->instance->is_moderator()) { - return false; - } - } else { - return roles::has_capability_in_course($this->recording->get('courseid'), 'mod/bigbluebuttonbn:managerecordings'); + $canmanagerecordings = roles::has_capability_in_course( + $this->recording->get('courseid'), 'mod/bigbluebuttonbn:managerecordings'); + $canviewallformats = roles::has_capability_in_course( + $this->recording->get('courseid'), 'mod/bigbluebuttonbn:viewallrecordingformats'); + $issafeformat = false; + // Now check the list of safe formats. + if ($safeformats = config::get('recording_safe_formats')) { + $safeformatarray = str_getcsv($safeformats); + $issafeformat = in_array($playback['type'], $safeformatarray); } - return true; - + return ($canmanagerecordings && $canviewallformats) || $issafeformat; } } diff --git a/mod/bigbluebuttonbn/classes/settings.php b/mod/bigbluebuttonbn/classes/settings.php index 49cf9ba6bff..038726d2902 100644 --- a/mod/bigbluebuttonbn/classes/settings.php +++ b/mod/bigbluebuttonbn/classes/settings.php @@ -377,6 +377,26 @@ class settings { $item, $recordingsetting ); + $recordingsafeformat = [ + 'notes' => get_string('view_recording_format_notes', 'mod_bigbluebuttonbn'), + 'podcast' => get_string('view_recording_format_podcast', 'mod_bigbluebuttonbn'), + 'presentation' => get_string('view_recording_format_presentation', 'mod_bigbluebuttonbn'), + 'screenshare' => get_string('view_recording_format_screenshare', 'mod_bigbluebuttonbn'), + 'statistics' => get_string('view_recording_format_statistics', 'mod_bigbluebuttonbn'), + 'video' => get_string('view_recording_format_video', 'mod_bigbluebuttonbn'), + ]; + $item = new admin_setting_configmultiselect( + 'bigbluebuttonbn_recording_safe_formats', + get_string('config_recording_safe_formats', 'mod_bigbluebuttonbn'), + get_string('config_recording_safe_formats_description', 'mod_bigbluebuttonbn'), + ['video', 'presentation'], + $recordingsafeformat + ); + $this->add_conditional_element( + 'recording_hide_button_editable', + $item, + $recordingsetting + ); } $this->admin->add($this->parent, $recordingsetting); } diff --git a/mod/bigbluebuttonbn/db/access.php b/mod/bigbluebuttonbn/db/access.php index 214532fc283..5e59ba1ed89 100644 --- a/mod/bigbluebuttonbn/db/access.php +++ b/mod/bigbluebuttonbn/db/access.php @@ -98,6 +98,17 @@ $capabilities = [ ], ], + // Ability to view all recordings formats (if not user can only see the formats + // defined in bigbluebuttonbn_recording_safe_formats). + 'mod/bigbluebuttonbn:viewallrecordingformats' => [ + 'captype' => 'read', + 'contextlevel' => CONTEXT_MODULE, + 'archetypes' => [ + 'manager' => CAP_ALLOW, + 'editingteacher' => CAP_ALLOW, + ], + ], + // Ability to publish recordings. 'mod/bigbluebuttonbn:publishrecordings' => [ 'captype' => 'write', diff --git a/mod/bigbluebuttonbn/lang/en/bigbluebuttonbn.php b/mod/bigbluebuttonbn/lang/en/bigbluebuttonbn.php index b09d2492bf5..cdcd5eff812 100644 --- a/mod/bigbluebuttonbn/lang/en/bigbluebuttonbn.php +++ b/mod/bigbluebuttonbn/lang/en/bigbluebuttonbn.php @@ -43,6 +43,7 @@ $string['bigbluebuttonbn:protectrecordings'] = 'Protect recordings'; $string['bigbluebuttonbn:unprotectrecordings'] = 'Unprotect recordings'; $string['bigbluebuttonbn:deleterecordings'] = 'Delete recordings'; $string['bigbluebuttonbn:importrecordings'] = 'Import recordings'; +$string['bigbluebuttonbn:viewallrecordingformats'] = 'View all recording formats'; $string['bigbluebuttonbn'] = 'BigBlueButton'; $string['bigbluebuttondisablednotification_subject'] = 'BigBlueButton activity module disabled.'; $string['bigbluebuttondisablednotification'] = 'The BigBlueButton activity module has been disabled and any existing BigBlueButton course activities are currently not accessible. Prior to re-enabling this plugin, please ensure that you have read and accepted the data processing agreement with Blindside Networks Inc.'; @@ -190,6 +191,11 @@ $string['config_recordings_preview_editable_description'] = 'Preview feature can $string['config_recordings_asc_sort'] = 'List recordings in chronological order'; $string['config_recordings_asc_sort_description'] = 'Recordings are ordered by date, either chronological or reverse chronological order (most recent recording first).'; +$string['config_recording_safe_formats'] = 'Recording formats viewable by everyone'; +$string['config_recording_safe_formats_description'] = 'Select the formats which are viewable by everyone. +All other formats are only viewable by teachers and other users with the mod/bigbluebuttonbn:viewallrecordingformats and + mod/bigbluebuttonbn:managerecordings capabilities.'; + $string['config_importrecordings'] = 'Import recordings'; $string['config_importrecordings_description'] = 'These settings are feature specific.'; $string['config_importrecordings_enabled'] = 'Import recordings enabled'; diff --git a/mod/bigbluebuttonbn/tests/generator/lib.php b/mod/bigbluebuttonbn/tests/generator/lib.php index 714c4b8ff3a..5328ba9ca40 100644 --- a/mod/bigbluebuttonbn/tests/generator/lib.php +++ b/mod/bigbluebuttonbn/tests/generator/lib.php @@ -258,7 +258,9 @@ class mod_bigbluebuttonbn_generator extends \testing_module_generator { ]); $mockdata['startTime'] = $data['starttime'] ?? $now; $mockdata['endTime'] = $data['endtime'] ?? $mockdata['startTime'] + HOURSECS; - + if (!empty($data['playback'])) { + $mockdata['playback'] = json_encode($data['playback']); + } if (!empty($data['isBreakout'])) { // If it is a breakout meeting, we do not have any way to know the real Id of the meeting // unless we query the list of submeetings. diff --git a/mod/bigbluebuttonbn/tests/output/recording_row_playback_test.php b/mod/bigbluebuttonbn/tests/output/recording_row_playback_test.php new file mode 100644 index 00000000000..b3df4f291d9 --- /dev/null +++ b/mod/bigbluebuttonbn/tests/output/recording_row_playback_test.php @@ -0,0 +1,151 @@ +. + +namespace mod_bigbluebuttonbn\output; + +use mod_bigbluebuttonbn\instance; +use mod_bigbluebuttonbn\recording; +use mod_bigbluebuttonbn\test\testcase_helper_trait; + +/** + * Recording row + * + * @package mod_bigbluebuttonbn + * @copyright 2010 onwards, Blindside Networks Inc + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @author Laurent David (laurent.david [at] call-learning [dt] fr) + */ +class recording_row_playback_test extends \advanced_testcase { + use testcase_helper_trait; + + /** + * Setup for test + */ + public function setUp(): void { + parent::setUp(); + $this->initialise_mock_server(); + } + + /** + * Recording sample data + */ + const RECORDING_DATA = [ + [ + 'status' => recording::RECORDING_STATUS_PROCESSED, + 'playback' => [ + 'format' => + [ + [ + + 'type' => 'podcast', + 'url' => 'http://mypodcast', + 'processingTime' => 0, + 'length' => 0, + + ], + [ + + 'type' => 'presentation', + 'url' => 'http://mypresentation', + 'processingTime' => 0, + 'length' => 0, + + ], + [ + + 'type' => 'video', + 'url' => 'http://myvideo', + 'processingTime' => 0, + 'length' => 0, + + ], + [ + + 'type' => 'settings', + 'url' => 'http://mysettings', + 'processingTime' => 0, + 'length' => 0, + + ] + ] + ] + ] + ]; + + /** + * Should this recording be included ? + * + * @param string $role + * @param array $canview + * @param object|null $globalsettings + * @return void + * @covers \recording_row_playback::should_be_included + * @dataProvider should_be_included_data_provider + */ + public function test_should_be_included(string $role, array $canview, object $globalsettings = null) { + global $PAGE; + $this->resetAfterTest(); + ['recordings' => $recordingsdata, 'activity' => $activity] = $this->create_activity_with_recordings( + $this->get_course(), + instance::TYPE_ALL, + self::RECORDING_DATA + ); + $user = $this->getDataGenerator()->create_user(); + $this->getDataGenerator()->enrol_user($user->id, $activity->course, $role); + if (!empty($globalsettings)) { + foreach ((array) $globalsettings as $key => $value) { + set_config($key, $value); + } + } + $this->setUser($user); + $recording = new recording(0, $recordingsdata[0]); + $rowplayback = new recording_row_playback($recording, instance::get_from_instanceid($activity->id)); + $rowinfo = $rowplayback->export_for_template($PAGE->get_renderer('mod_bigbluebuttonbn')); + $playbacktypes = array_map(function($playback) { + foreach ($playback->attributes as $attributearray) { + if (in_array('data-target', $attributearray)) { + return $attributearray['value']; + } + } + return ''; + }, $rowinfo->playbacks); + $this->assertEmpty(array_diff($canview, $playbacktypes)); + } + + /** + * Data provider for the should be included method + * + * @return array + */ + public function should_be_included_data_provider() { + return [ + 'editingteacher user should see all' => [ + 'role' => 'editingteacher', + 'canview' => ['video', 'presentation', 'podcast', 'settings'], + ], + 'student can see only default' => [ + 'role' => 'student', + 'canview' => ['video', 'presentation'], + ], + 'student can see only default except when we add more format to all users' => [ + 'role' => 'student', + 'canview' => ['video', 'presentation', 'settings'], + 'globalsettings' => (object) ['bigbluebuttonbn_recording_safe_formats' => 'video,presentation,settings'] + ] + + ]; + } +} diff --git a/mod/bigbluebuttonbn/version.php b/mod/bigbluebuttonbn/version.php index 89c018495a6..362d60a82c8 100644 --- a/mod/bigbluebuttonbn/version.php +++ b/mod/bigbluebuttonbn/version.php @@ -27,6 +27,6 @@ defined('MOODLE_INTERNAL') || die; -$plugin->version = 2023021300; +$plugin->version = 2023032200; $plugin->requires = 2022111800; $plugin->component = 'mod_bigbluebuttonbn';