From 81f6ecd362f279a0e1c468bff68c98ea2571bd2e Mon Sep 17 00:00:00 2001 From: Angie Byron Date: Mon, 3 Jan 2011 02:41:33 +0000 Subject: #686060 by Crell, David_Rothstein, dww, yoroy, carlos8f, et al: Fixed Explain that the Update manager only works if you have FTP or SSH access to your server --- modules/update/tests/update_test.module | 37 ++++++++++ modules/update/update.manager.inc | 121 +++++++++++++++++++++++++++++++- modules/update/update.test | 4 +- 3 files changed, 158 insertions(+), 4 deletions(-) (limited to 'modules/update') diff --git a/modules/update/tests/update_test.module b/modules/update/tests/update_test.module index 9b8de5b45..fb7d3abfb 100644 --- a/modules/update/tests/update_test.module +++ b/modules/update/tests/update_test.module @@ -112,3 +112,40 @@ function update_test_archiver_info() { ), ); } + +/** + * Implements hook_filetransfer_info(). + */ +function update_test_filetransfer_info() { + // Define a mock file transfer method, to ensure that there will always be + // at least one method available in the user interface (regardless of the + // environment in which the update manager tests are run). + return array( + 'system_test' => array( + 'title' => t('Update Test FileTransfer'), + // This should be in an .inc file, but for testing purposes, it is OK to + // leave it in the main module file. + 'file' => 'update_test.module', + 'class' => 'UpdateTestFileTransfer', + 'weight' => -20, + ), + ); +} + +/** + * Mock FileTransfer object to test the settings form functionality. + */ +class UpdateTestFileTransfer { + public static function factory() { + return new UpdateTestFileTransfer; + } + + public function getSettingsForm() { + $form = array(); + $form['udpate_test_username'] = array( + '#type' => 'textfield', + '#title' => t('Update Test Username'), + ); + return $form; + } +} diff --git a/modules/update/update.manager.inc b/modules/update/update.manager.inc index 0e699522a..9f0fb8cb7 100644 --- a/modules/update/update.manager.inc +++ b/modules/update/update.manager.inc @@ -59,6 +59,10 @@ * The form array for selecting which projects to update. */ function update_manager_update_form($form, $form_state = array(), $context) { + if (!_update_manager_check_backends($form, 'update')) { + return $form; + } + $form['#theme'] = 'update_manager_update_form'; $available = update_get_available(TRUE); @@ -354,6 +358,10 @@ function update_manager_download_batch_finished($success, $results) { * file transfer credentials and attempt to complete the update. */ function update_manager_update_ready_form($form, &$form_state) { + if (!_update_manager_check_backends($form, 'update')) { + return $form; + } + $form['backup'] = array( '#prefix' => '', '#markup' => t('Back up your database and site before you continue. Learn how.', array('@backup_url' => url('http://drupal.org/node/22281'))), @@ -461,11 +469,18 @@ function update_manager_update_ready_form_submit($form, &$form_state) { * The form array for selecting which project to install. */ function update_manager_install_form($form, &$form_state, $context) { - $form = array(); + if (!_update_manager_check_backends($form, 'install')) { + return $form; + } $form['help_text'] = array( '#prefix' => '

', - '#markup' => t('To install a new module or theme, either enter the URL of an archive file you wish to install, or upload the archive file that you have downloaded. You can find modules and themes at http://drupal.org.
The following archive extensions are supported: %extensions.', array('@module_url' => 'http://drupal.org/project/modules', '@theme_url' => 'http://drupal.org/project/themes', '@drupal_org_url' => 'http://drupal.org', '%extensions' => archiver_get_extensions())), + '#markup' => t('You can find modules and themes on drupal.org. The following file extensions are supported: %extensions.', array( + '@module_url' => 'http://drupal.org/project/modules', + '@theme_url' => 'http://drupal.org/project/themes', + '@drupal_org_url' => 'http://drupal.org', + '%extensions' => archiver_get_extensions(), + )), '#suffix' => '

', ); @@ -496,6 +511,73 @@ function update_manager_install_form($form, &$form_state, $context) { return $form; } +/** + * Checks for file transfer backends and prepares a form fragment about them. + * + * @param array $form + * Reference to the form array we're building. + * @param string $operation + * The Update manager operation we're in the middle of. Can be either + * 'update' or 'install'. Use to provide operation-specific interface text. + * + * @return + * TRUE if the Update manager should continue to the next step in the + * workflow, or FALSE if we've hit a fatal configuration and must halt the + * workflow. + */ +function _update_manager_check_backends(&$form, $operation) { + // If file transfers will be performed locally, we do not need to display any + // warnings or notices to the user and should automatically continue the + // workflow, since we won't be using a FileTransfer backend that requires + // user input or a specific server configuration. + if (update_manager_local_transfers_allowed()) { + return TRUE; + } + + // Otherwise, show the available backends. + $form['available_backends'] = array( + '#prefix' => '

', + '#suffix' => '

', + ); + + $available_backends = drupal_get_filetransfer_info(); + if (empty($available_backends)) { + if ($operation == 'update') { + $form['available_backends']['#markup'] = t('Your server does not support updating modules and themes from this interface. Instead, update modules and themes by uploading the new versions directly to the server, as described in the handbook.', array('@handbook_url' => 'http://drupal.org/getting-started/install-contrib')); + } + else { + $form['available_backends']['#markup'] = t('Your server does not support installing modules and themes from this interface. Instead, install modules and themes by uploading them directly to the server, as described in the handbook.', array('@handbook_url' => 'http://drupal.org/getting-started/install-contrib')); + } + return FALSE; + } + + $backend_names = array(); + foreach ($available_backends as $backend) { + $backend_names[] = $backend['title']; + } + if ($operation == 'update') { + $form['available_backends']['#markup'] = format_plural( + count($available_backends), + 'Updating modules and themes requires @backends access to your server. See the handbook for other update methods.', + 'Updating modules and themes requires access to your server via one of the following methods: @backends. See the handbook for other update methods.', + array( + '@backends' => implode(', ', $backend_names), + '@handbook_url' => 'http://drupal.org/getting-started/install-contrib', + )); + } + else { + $form['available_backends']['#markup'] = format_plural( + count($available_backends), + 'Installing modules and themes requires @backends access to your server. See the handbook for other installation methods.', + 'Installing modules and themes requires access to your server via one of the following methods: @backends. See the handbook for other installation methods.', + array( + '@backends' => implode(', ', $backend_names), + '@handbook_url' => 'http://drupal.org/getting-started/install-contrib', + )); + } + return TRUE; +} + /** * Validate the form for installing a new project via the update manager. */ @@ -811,6 +893,41 @@ function update_manager_batch_project_get($project, $url, &$context) { $context['finished'] = 1; } +/** + * Determines if file transfers will be performed locally. + * + * If the server is configured such that webserver-created files have the same + * owner as the configuration directory (e.g. sites/default) where new code + * will eventually be installed, the Update manager can transfer files entirely + * locally, without changing their ownership (in other words, without prompting + * the user for FTP, SSH or other credentials). + * + * This server configuration is an inherent security weakness because it allows + * a malicious webserver process to append arbitrary PHP code and then execute + * it. However, it is supported here because it is a common configuration on + * shared hosting, and there is nothing Drupal can do to prevent it. + * + * @return + * TRUE if local file transfers are allowed on this server, or FALSE if not. + * + * @see update_manager_update_ready_form_submit() + * @see update_manager_install_form_submit() + * @see install_check_requirements() + */ +function update_manager_local_transfers_allowed() { + // Compare the owner of a webserver-created temporary file to the owner of + // the configuration directory to determine if local transfers will be + // allowed. + $temporary_file = drupal_tempnam('temporary://', 'update_'); + $local_transfers_allowed = fileowner($temporary_file) === fileowner(conf_path()); + + // Clean up. If this fails, we can ignore it (since this is just a temporary + // file anyway). + @drupal_unlink($temporary_file); + + return $local_transfers_allowed; +} + /** * @} End of "defgroup update_manager_file". */ diff --git a/modules/update/update.test b/modules/update/update.test index 840371552..a1252dcde 100644 --- a/modules/update/update.test +++ b/modules/update/update.test @@ -622,9 +622,9 @@ class UpdateTestUploadCase extends UpdateTestHelper { function testFileNameExtensionMerging() { $this->drupalGet('admin/modules/install'); // Make sure the bogus extension supported by update_test.module is there. - $this->assertPattern('/archive extensions are supported:.*update-test-extension/', t("Found 'update-test-extension' extension")); + $this->assertPattern('/file extensions are supported:.*update-test-extension/', t("Found 'update-test-extension' extension")); // Make sure it didn't clobber the first option from core. - $this->assertPattern('/archive extensions are supported:.*tar/', t("Found 'tar' extension")); + $this->assertPattern('/file extensions are supported:.*tar/', t("Found 'tar' extension")); } /** -- cgit v1.2.3