Permalink
Browse files

MDL-35238 Implement deployment authorization

The caller of the mdeploy.php utility is expected to create a file in
the data directory. The name of such file and the passphrase in it are
then sent to mdeploy.php as a part of the request. The submitted and
stored values are then compared.
  • Loading branch information...
1 parent 6aa2e28 commit 3daedb5c5a0adb4074bc5b6107f4759899145630 @mudrd8mz mudrd8mz committed Oct 9, 2012
Showing with 105 additions and 8 deletions.
  1. +58 −2 lib/pluginlib.php
  2. +23 −1 lib/tests/pluginlib_test.php
  3. +24 −5 mdeploy.php
View
@@ -1589,6 +1589,8 @@ public function make_execution_widget(available_update_info $info) {
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 @@ public function make_execution_widget(available_update_info $info) {
'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 @@ public function __call($name, array $arguments = array()) {
}
}
+ /**
+ * 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 @@ protected function params_to_data(array $params) {
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();
+ }
}
@@ -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);
+ }
}
View
@@ -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 @@ protected function authorize() {
$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.');
}
}

0 comments on commit 3daedb5

Please sign in to comment.