commit ab697c4752ffb5bdbc26a48884fdd3d56596c096 Author: Pieter Frenssen Date: Thu Apr 24 15:00:48 2014 +0200 2238703-11 diff --git a/content_lock.module b/content_lock.module index da4e241..704cf56 100644 --- a/content_lock.module +++ b/content_lock.module @@ -79,6 +79,12 @@ function content_lock_menu() { 'page arguments' => array(2, FALSE, FALSE), 'access callback' => true ); + $items['ajax/content_lock/%node/lock/%'] = array ( + 'page callback' => 'content_lock_node_ajax_callback', + 'page arguments' => array(2, 4), + 'access callback' => 'user_access', + 'access arguments' => array('check out documents'), + ); $items['admin/config/content/content_lock'] = array ( 'type' => MENU_NORMAL_ITEM, 'title' => 'Content lock', @@ -93,17 +99,6 @@ function content_lock_menu() { } /** - * Implements hook_menu_site_status_alter(). - */ -function content_lock_menu_site_status_alter(&$page_callback_result, $path) { - if (content_lock_is_path_protected($path)) { - if (!isset($_GET['content_lock_token']) || !drupal_valid_token($_GET['content_lock_token'], $path)) { - $page_callback_result = MENU_ACCESS_DENIED; - } - } -} - -/** * Check if an internal Drupal path should be protected with a token. * * Adds requirements that certain path be accessed only through tokenized URIs @@ -158,6 +153,7 @@ function content_lock_content_lock_path_protected($path) { * Implements hook_preprocess_link(). */ function content_lock_preprocess_link(&$vars) { + // Append a CSRF token to all paths that show the node add/edit form. if (content_lock_is_path_protected($vars['path'])) { if (!isset($vars['options']['query']['content_lock_token'])) { $vars['options']['query']['content_lock_token'] = drupal_get_token($vars['path']); @@ -312,12 +308,6 @@ function content_lock_form_alter(&$form, &$form_state, $form_id) { } if ($skip_lock == FALSE) { - // if we should lock or already have been locked, load the unload js. Dont use - // form alter but rather after build, so it works even for previews - if(variable_get('content_lock_unload_js', true)) { - $form['#after_build'][] = '_content_lock_add_unload_js'; - } - // Adding cancel button, if configured if(variable_get('content_lock_admin_cancelbutton', true)) { _content_lock_add_cancelbutton($form, $form_state, $form_id); @@ -329,27 +319,40 @@ function content_lock_form_alter(&$form, &$form_state, $form_id) { // If the form did not get submitted we show it the first time // so try to get the lock if possible else if ($form_state['submitted'] === FALSE) { - // Refuse to lock if this page loaded is not protected. - // Protects against accidental introduction of CSRF - // (user locking nodes without knowing it and causing DoS) by any - // module rendering the node form at a menu path other than - // 'node/%node/edit'. + // Refuse to lock if a CSRF token is not present. This prevents a denial + // of service attack if a user is tricked into visiting pages that cause + // nodes to be locked. + // A CSRF token may be missing in these cases: + // - A node add/edit form is displayed on a path that is not protected + // with a CSRF token. Please add the path to the list of protected + // paths by implementing hook_content_lock_path_protected(). + // - The CSRF token has not been added to the URL as a query parameter, + // for example because the user entered node/1/edit directly in the + // address bar of the browser. To avoid CSRF attacks we do not lock + // the node automatically, but still give the user the possibility to + // lock the node manually using an AJAX call with a proper CSRF token. $menu_item = menu_get_item(); - if (!content_lock_is_path_protected($menu_item['path'])) { - $node_title = empty($node->title) ? 'node/' . $nid : $node->title; - drupal_set_message(t('Unable to lock !node for editing because this page load was not protected. You still may !edit node using the normal node editing page.', array( - '!node' => l(t('%node_title', array('%node_title' => $node_title)), 'node/' . $nid, array('html' => TRUE)), - '!edit node' => l(t('edit %node_title', array('%node_title' => $node_title)), 'node/' . $nid . '/edit', array('html' => TRUE)), + if (empty($_GET['content_lock_token']) || !drupal_valid_token($_GET['content_lock_token'], $menu_item['href'])) { + drupal_set_message(t('The page you are editing could not be locked automatically. Please !link to make sure other people cannot accidentally overwrite your changes.', array( + '!link' => l(t('lock the page'), 'nojs/content_lock/' . $nid . '/lock/' . drupal_get_token($nid), array('attributes' => array('class' => array('use-ajax')))), )), 'error'); - watchdog('content_lock', 'Attempt to load the node_form form at menu path %path which is not protected from CSRF. Developers who want to create custom node editing pages and protect them with hook_content_lock_path_protected() or use protection_menu_token module to protect this path.', array('%path' => $menu_item['path']), WATCHDOG_WARNING); - drupal_goto($destination); + if (!content_lock_is_path_protected($menu_item['path'])) { + watchdog('content_lock', 'Attempt to load the node_form form at menu path %path which is not protected from CSRF. Developers who want to create custom node editing pages and protect them with hook_content_lock_path_protected() or use protection_menu_token module to protect this path.', array('%path' => $menu_item['path']), WATCHDOG_WARNING); + } } else { // Finally set the lock if everything passed. - if (content_lock_node($nid, $user->uid) == false) { + if (content_lock_node($nid, $user->uid) == FALSE) { // Could not lock node, it's locked by someone else. drupal_goto($destination); } + // If we should lock or already have been locked, load the unload js. + // Don't use form alter but rather after build, so it works even for + // previews. + elseif (variable_get('content_lock_unload_js', TRUE)) { + $form['#after_build'][] = '_content_lock_add_unload_js'; + } + } } // else if($form_state['submitted'] === TRUE) @@ -562,6 +565,35 @@ function content_lock_user_logout($account) { } /** + * AJAX callback to lock a node manually. + * + * @param object $node + * The node to lock. + * @param string $token + * The CSRF token. + */ +function content_lock_node_ajax_callback($node, $token) { + global $user; + + // Only lock the node if we have a valid CSRF token. + if (drupal_valid_token($token, $node->nid)) { + content_lock_node($node->nid, $user->uid); + + // Add the javascript that unlocks the node when the user navigates away + // from the page. + $form = array('nid' => array('#value' => $node->nid)); + _content_lock_add_unload_js($form, array()); + } + else { + drupal_set_message(t('The content could not be locked.')); + } + $commands = array(); + $commands[] = ajax_command_remove('div.messages'); + $commands[] = ajax_command_before('#main-content', theme('status_messages')); + ajax_deliver(array('#type' => 'ajax', '#commands' => $commands)); +} + +/** * Try to lock a document for editing. * * If the lock exists, a new AJAX unlock key is created to combat AJAX