diff --git a/lib/pluginlib.php b/lib/pluginlib.php index d2a66c74362..46716ab1357 100644 --- a/lib/pluginlib.php +++ b/lib/pluginlib.php @@ -1589,6 +1589,8 @@ class available_update_deployer { throw new coding_exception('Unknown plugin type root location', $plugintype); } + list($passfile, $password) = $this->prepare_authorization(); + $params = array( 'upgrade' => true, 'type' => $plugintype, @@ -1597,8 +1599,8 @@ class available_update_deployer { 'download' => $info->download, 'dataroot' => $CFG->dataroot, 'dirroot' => $CFG->dirroot, - 'passfile' => '', // TODO - 'password' => '', // TODO + 'passfile' => $passfile, + 'password' => $password, ); $widget = new single_button( @@ -1698,6 +1700,42 @@ class available_update_deployer { } } + /** + * Generates a random token and stores it in a file in moodledata directory. + * + * @return array of the (string)filename and (string)password in this order + */ + public function prepare_authorization() { + global $CFG; + + make_upload_directory('mdeploy/auth/'); + + $attempts = 0; + $success = false; + + while (!$success and $attempts < 5) { + $attempts++; + + $passfile = $this->generate_passfile(); + $password = $this->generate_password(); + $now = time(); + + $filepath = $CFG->dataroot.'/mdeploy/auth/'.$passfile; + + if (!file_exists($filepath)) { + $success = file_put_contents($filepath, $password . PHP_EOL . $now . PHP_EOL, LOCK_EX); + } + } + + if ($success) { + return array($passfile, $password); + + } else { + throw new moodle_exception('unable_prepare_authorization', 'core_plugin'); + } + } + + // End of external API /** @@ -1753,6 +1791,24 @@ class available_update_deployer { return $data; } + + /** + * Returns a random string to be used as a filename of the password storage. + * + * @return string + */ + protected function generate_passfile() { + return clean_param(uniqid('mdeploy_', true), PARAM_FILE); + } + + /** + * Returns a random string to be used as the authorization token + * + * @return string + */ + protected function generate_password() { + return complex_random_string(); + } } diff --git a/lib/tests/pluginlib_test.php b/lib/tests/pluginlib_test.php index 62db9e44781..1552d9369c7 100644 --- a/lib/tests/pluginlib_test.php +++ b/lib/tests/pluginlib_test.php @@ -545,15 +545,37 @@ class testable_available_update_checker_cron_executed extends Exception { } +/** + * Modified {@link available_update_deployer} suitable for testing purposes + */ +class testable_available_update_deployer extends available_update_deployer { + +} + + /** * Test cases for {@link available_update_deployer} class */ class available_update_deployer_test extends advanced_testcase { public function test_magic_setters() { - $deployer = available_update_deployer::instance(); + $deployer = testable_available_update_deployer::instance(); $value = new moodle_url('/'); $deployer->set_returnurl($value); $this->assertSame($deployer->get_returnurl(), $value); } + + public function test_prepare_authorization() { + global $CFG; + + $deployer = testable_available_update_deployer::instance(); + list($passfile, $password) = $deployer->prepare_authorization(); + $filename = $CFG->phpunit_dataroot.'/mdeploy/auth/'.$passfile; + $this->assertFileExists($filename); + $stored = file($filename, FILE_IGNORE_NEW_LINES); + $this->assertEquals(count($stored), 2); + $this->assertGreaterThan(23, strlen($stored[0])); + $this->assertSame($stored[0], $password); + $this->assertTrue(time() - (int)$stored[1] < 60); + } } diff --git a/mdeploy.php b/mdeploy.php index 3b7c3f8b471..689f7267f9f 100644 --- a/mdeploy.php +++ b/mdeploy.php @@ -453,7 +453,7 @@ class input_http_provider extends input_provider { * @return array of raw values passed via HTTP request */ protected function parse_raw_options() { - return $_GET; // TODO switch to $_POST + return $_POST; } } @@ -629,15 +629,34 @@ class worker extends singleton_pattern { $passfile = $this->input->get_option('passfile'); $password = $this->input->get_option('password'); - $passpath = $dataroot.'/temp/mdeploy/'.$passfile; + $passpath = $dataroot.'/mdeploy/auth/'.$passfile; if (!is_readable($passpath)) { - throw new unauthorized_access_exception('Unable to read passphrase file.'); + throw new unauthorized_access_exception('Unable to read the passphrase file.'); } - $stored = file_get_contents($passpath); + $stored = file($passpath, FILE_IGNORE_NEW_LINES); - if ($password !== $stored) { + // "This message will self-destruct in five seconds." -- Mission Commander Swanbeck, Mission: Impossible II + unlink($passpath); + + if (is_readable($passpath)) { + throw new unauthorized_access_exception('Unable to remove the passphrase file.'); + } + + if (count($stored) < 2) { + throw new unauthorized_access_exception('Invalid format of the passphrase file.'); + } + + if (time() - (int)$stored[1] > 30 * 60) { + throw new unauthorized_access_exception('Passphrase timeout.'); + } + + if (strlen($stored[0]) < 24) { + throw new unauthorized_access_exception('Session passphrase not long enough.'); + } + + if ($password !== $stored[0]) { throw new unauthorized_access_exception('Session passphrase does not match the stored one.'); } }