Plugins: Move capability checks further up in `wp_ajax_update_plugin()` and `wp_ajax_delete_plugin()`.

Add tests for both Ajax handlers.

Props Yorick Koster, swissspidy.
Fixes #37490.

git-svn-id: https://develop.svn.wordpress.org/trunk@38168 602fd350-edb4-49c9-b593-d223f7449a82
This commit is contained in:
Dominik Schilling 2016-07-27 17:42:01 +00:00
parent d097c1d916
commit af6b1a5388
5 changed files with 376 additions and 26 deletions

View File

@ -3653,28 +3653,29 @@ function wp_ajax_update_plugin() {
) );
}
$plugin = plugin_basename( sanitize_text_field( wp_unslash( $_POST['plugin'] ) ) );
$plugin_data = get_plugin_data( WP_PLUGIN_DIR . '/' . $plugin );
$plugin = plugin_basename( sanitize_text_field( wp_unslash( $_POST['plugin'] ) ) );
$status = array(
'update' => 'plugin',
'plugin' => $plugin,
'slug' => sanitize_key( wp_unslash( $_POST['slug'] ) ),
'pluginName' => $plugin_data['Name'],
'oldVersion' => '',
'newVersion' => '',
);
if ( ! current_user_can( 'update_plugins' ) || 0 !== validate_file( $plugin ) ) {
$status['errorMessage'] = __( 'Sorry, you are not allowed to update plugins for this site.' );
wp_send_json_error( $status );
}
$plugin_data = get_plugin_data( WP_PLUGIN_DIR . '/' . $plugin );
$status['plugin'] = $plugin;
$status['pluginName'] = $plugin_data['Name'];
if ( $plugin_data['Version'] ) {
/* translators: %s: Plugin version */
$status['oldVersion'] = sprintf( __( 'Version %s' ), $plugin_data['Version'] );
}
if ( ! current_user_can( 'update_plugins' ) ) {
$status['errorMessage'] = __( 'Sorry, you are not allowed to update plugins for this site.' );
wp_send_json_error( $status );
}
include_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php';
wp_update_plugins();
@ -3748,24 +3749,29 @@ function wp_ajax_delete_plugin() {
check_ajax_referer( 'updates' );
if ( empty( $_POST['slug'] ) || empty( $_POST['plugin'] ) ) {
wp_send_json_error( array( 'errorCode' => 'no_plugin_specified' ) );
wp_send_json_error( array(
'slug' => '',
'errorCode' => 'no_plugin_specified',
'errorMessage' => __( 'No plugin specified.' ),
) );
}
$plugin = plugin_basename( sanitize_text_field( wp_unslash( $_POST['plugin'] ) ) );
$plugin_data = get_plugin_data( WP_PLUGIN_DIR . '/' . $plugin );
$plugin = plugin_basename( sanitize_text_field( wp_unslash( $_POST['plugin'] ) ) );
$status = array(
'delete' => 'plugin',
'slug' => sanitize_key( wp_unslash( $_POST['slug'] ) ),
'plugin' => $plugin,
'pluginName' => $plugin_data['Name'],
'delete' => 'plugin',
'slug' => sanitize_key( wp_unslash( $_POST['slug'] ) ),
);
if ( ! current_user_can( 'delete_plugins' ) ) {
if ( ! current_user_can( 'delete_plugins' ) || 0 !== validate_file( $plugin ) ) {
$status['errorMessage'] = __( 'Sorry, you are not allowed to delete plugins for this site.' );
wp_send_json_error( $status );
}
$plugin_data = get_plugin_data( WP_PLUGIN_DIR . '/' . $plugin );
$status['plugin'] = $plugin;
$status['pluginName'] = $plugin_data['Name'];
if ( is_plugin_active( $plugin ) ) {
$status['errorMessage'] = __( 'You cannot delete a plugin while it is active on the main site.' );
wp_send_json_error( $status );

View File

@ -447,7 +447,11 @@
errorMessage = wp.updates.l10n.updateFailed.replace( '%s', response.errorMessage );
if ( 'plugins' === pagenow || 'plugins-network' === pagenow ) {
$message = $( 'tr[data-plugin="' + response.plugin + '"]' ).find( '.update-message' );
if ( response.plugin ) {
$message = $( 'tr[data-plugin="' + response.plugin + '"]' ).find( '.update-message' );
} else {
$message = $( 'tr[data-slug="' + response.slug + '"]' ).find( '.update-message' );
}
$message.removeClass( 'updating-message notice-warning' ).addClass( 'notice-error' ).find( 'p' ).html( errorMessage );
} else if ( 'plugin-install' === pagenow || 'plugin-install-network' === pagenow ) {
$card = $( '.plugin-card-' + response.slug )
@ -458,9 +462,13 @@
} ) );
$card.find( '.update-now' )
.attr( 'aria-label', wp.updates.l10n.updateFailedLabel.replace( '%s', response.pluginName ) )
.text( wp.updates.l10n.updateFailedShort ).removeClass( 'updating-message' );
if ( response.pluginName ) {
$card.find( '.update-now' )
.attr( 'aria-label', wp.updates.l10n.updateFailedLabel.replace( '%s', response.pluginName ) );
}
$card.on( 'click', '.notice.is-dismissible .notice-dismiss', function() {
// Use same delay as the total duration of the notice fadeTo + slideUp animation.
@ -814,14 +822,21 @@
* @param {string} response.errorMessage The error that occurred.
*/
wp.updates.deletePluginError = function( response ) {
var $plugin = $( 'tr.inactive[data-plugin="' + response.plugin + '"]' ),
var $plugin, $pluginUpdateRow,
pluginUpdateRow = wp.template( 'item-update-row' ),
$pluginUpdateRow = $plugin.siblings( '[data-plugin="' + response.plugin + '"]' ),
noticeContent = wp.updates.adminNotice( {
className: 'update-message notice-error notice-alt',
message: response.errorMessage
} );
if ( response.plugin ) {
$plugin = $( 'tr.inactive[data-plugin="' + response.plugin + '"]' );
$pluginUpdateRow = $plugin.siblings( '[data-plugin="' + response.plugin + '"]' );
} else {
$plugin = $( 'tr.inactive[data-slug="' + response.slug + '"]' );
$pluginUpdateRow = $plugin.siblings( '[data-slug="' + response.slug + '"]' );
}
if ( ! wp.updates.isValidResponse( response, 'delete' ) ) {
return;
}
@ -835,7 +850,7 @@
$plugin.addClass( 'update' ).after(
pluginUpdateRow( {
slug: response.slug,
plugin: response.plugin,
plugin: response.plugin || response.slug,
colspan: $( '#bulk-action-form' ).find( 'thead th:not(.hidden), thead td' ).length,
content: noticeContent
} )

View File

@ -18,13 +18,13 @@ abstract class WP_Ajax_UnitTestCase extends WP_UnitTestCase {
/**
* Last AJAX response. This is set via echo -or- wp_die.
* @var type
* @var string
*/
protected $_last_response = '';
/**
* List of ajax actions called via POST
* @var type
* @var array
*/
protected static $_core_actions_get = array(
'fetch-list', 'ajax-tag-search', 'wp-compression-test', 'imgedit-preview', 'oembed-cache',
@ -39,7 +39,7 @@ abstract class WP_Ajax_UnitTestCase extends WP_UnitTestCase {
/**
* List of ajax actions called via GET
* @var type
* @var array
*/
protected static $_core_actions_post = array(
'oembed_cache', 'image-editor', 'delete-comment', 'delete-tag', 'delete-link',
@ -53,7 +53,9 @@ abstract class WP_Ajax_UnitTestCase extends WP_UnitTestCase {
'wp-remove-post-lock', 'dismiss-wp-pointer', 'send-attachment-to-editor', 'heartbeat', 'nopriv_heartbeat', 'get-revision-diffs',
'save-user-color-scheme', 'update-widget', 'query-themes', 'parse-embed', 'set-attachment-thumbnail',
'parse-media-shortcode', 'destroy-sessions', 'install-plugin', 'update-plugin', 'press-this-save-post',
'press-this-add-category', 'crop-image', 'generate-password',
'press-this-add-category', 'crop-image', 'generate-password', 'save-wporg-username', 'delete-plugin',
'search-plugins', 'search-install-plugins', 'activate-plugin', 'update-theme', 'delete-theme',
'install-theme', 'get-post-thumbnail-html',
);
public static function setUpBeforeClass() {

View File

@ -0,0 +1,158 @@
<?php
/**
* Admin ajax functions to be tested
*/
require_once( ABSPATH . 'wp-admin/includes/ajax-actions.php' );
/**
* Testing Ajax handler for deleting a plugin.
*
* @group ajax
*/
class Tests_Ajax_Delete_Plugin extends WP_Ajax_UnitTestCase {
/**
* @expectedException WPAjaxDieStopException
* @expectedExceptionMessage -1
*/
public function test_missing_nonce() {
$this->_handleAjax( 'delete-plugin' );
}
public function test_missing_plugin() {
$_POST['_ajax_nonce'] = wp_create_nonce( 'updates' );
$_POST['slug'] = 'foo';
// Make the request
try {
$this->_handleAjax( 'delete-plugin' );
} catch ( WPAjaxDieContinueException $e ) {
unset( $e );
}
// Get the response.
$response = json_decode( $this->_last_response, true );
$expected = array(
'success' => false,
'data' => array(
'slug' => '',
'errorCode' => 'no_plugin_specified',
'errorMessage' => 'No plugin specified.',
),
);
$this->assertEqualSets( $expected, $response );
}
public function test_missing_slug() {
$_POST['_ajax_nonce'] = wp_create_nonce( 'updates' );
$_POST['plugin'] = 'foo/bar.php';
// Make the request
try {
$this->_handleAjax( 'delete-plugin' );
} catch ( WPAjaxDieContinueException $e ) {
unset( $e );
}
// Get the response.
$response = json_decode( $this->_last_response, true );
$expected = array(
'success' => false,
'data' => array(
'slug' => '',
'errorCode' => 'no_plugin_specified',
'errorMessage' => 'No plugin specified.',
),
);
$this->assertEqualSets( $expected, $response );
}
public function test_missing_capability() {
$_POST['_ajax_nonce'] = wp_create_nonce( 'updates' );
$_POST['plugin'] = 'foo/bar.php';
$_POST['slug'] = 'foo';
// Make the request
try {
$this->_handleAjax( 'delete-plugin' );
} catch ( WPAjaxDieContinueException $e ) {
unset( $e );
}
// Get the response.
$response = json_decode( $this->_last_response, true );
$expected = array(
'success' => false,
'data' => array(
'delete' => 'plugin',
'slug' => 'foo',
'errorMessage' => 'Sorry, you are not allowed to delete plugins for this site.',
),
);
$this->assertEqualSets( $expected, $response );
}
public function test_invalid_file() {
$this->_setRole( 'administrator' );
$_POST['_ajax_nonce'] = wp_create_nonce( 'updates' );
$_POST['plugin'] = '../foo/bar.php';
$_POST['slug'] = 'foo';
// Make the request
try {
$this->_handleAjax( 'delete-plugin' );
} catch ( WPAjaxDieContinueException $e ) {
unset( $e );
}
// Get the response.
$response = json_decode( $this->_last_response, true );
$expected = array(
'success' => false,
'data' => array(
'delete' => 'plugin',
'slug' => 'foo',
'errorMessage' => 'Sorry, you are not allowed to delete plugins for this site.',
),
);
$this->assertEqualSets( $expected, $response );
}
public function test_delete_plugin() {
$this->_setRole( 'administrator' );
$_POST['_ajax_nonce'] = wp_create_nonce( 'updates' );
$_POST['plugin'] = 'foo.php';
$_POST['slug'] = 'foo';
// Make the request
try {
$this->_handleAjax( 'delete-plugin' );
} catch ( WPAjaxDieContinueException $e ) {
unset( $e );
}
// Get the response.
$response = json_decode( $this->_last_response, true );
$expected = array(
'success' => true,
'data' => array(
'delete' => 'plugin',
'slug' => 'foo',
'plugin' => 'foo.php',
'pluginName' => '',
),
);
$this->assertEqualSets( $expected, $response );
}
}

View File

@ -0,0 +1,169 @@
<?php
/**
* Admin ajax functions to be tested
*/
require_once( ABSPATH . 'wp-admin/includes/ajax-actions.php' );
/**
* Testing Ajax handler for updating a plugin.
*
* @group ajax
*/
class Tests_Ajax_Update_Plugin extends WP_Ajax_UnitTestCase {
/**
* @expectedException WPAjaxDieStopException
* @expectedExceptionMessage -1
*/
public function test_missing_nonce() {
$this->_handleAjax( 'update-plugin' );
}
public function test_missing_plugin() {
$_POST['_ajax_nonce'] = wp_create_nonce( 'updates' );
$_POST['slug'] = 'foo';
// Make the request
try {
$this->_handleAjax( 'update-plugin' );
} catch ( WPAjaxDieContinueException $e ) {
unset( $e );
}
// Get the response.
$response = json_decode( $this->_last_response, true );
$expected = array(
'success' => false,
'data' => array(
'slug' => '',
'errorCode' => 'no_plugin_specified',
'errorMessage' => 'No plugin specified.',
),
);
$this->assertEqualSets( $expected, $response );
}
public function test_missing_slug() {
$_POST['_ajax_nonce'] = wp_create_nonce( 'updates' );
$_POST['plugin'] = 'foo/bar.php';
// Make the request
try {
$this->_handleAjax( 'update-plugin' );
} catch ( WPAjaxDieContinueException $e ) {
unset( $e );
}
// Get the response.
$response = json_decode( $this->_last_response, true );
$expected = array(
'success' => false,
'data' => array(
'slug' => '',
'errorCode' => 'no_plugin_specified',
'errorMessage' => 'No plugin specified.',
),
);
$this->assertEqualSets( $expected, $response );
}
public function test_missing_capability() {
$_POST['_ajax_nonce'] = wp_create_nonce( 'updates' );
$_POST['plugin'] = 'foo/bar.php';
$_POST['slug'] = 'foo';
// Make the request
try {
$this->_handleAjax( 'update-plugin' );
} catch ( WPAjaxDieContinueException $e ) {
unset( $e );
}
// Get the response.
$response = json_decode( $this->_last_response, true );
$expected = array(
'success' => false,
'data' => array(
'update' => 'plugin',
'slug' => 'foo',
'errorMessage' => 'Sorry, you are not allowed to update plugins for this site.',
'oldVersion' => '',
'newVersion' => '',
),
);
$this->assertEqualSets( $expected, $response );
}
public function test_invalid_file() {
$this->_setRole( 'administrator' );
$_POST['_ajax_nonce'] = wp_create_nonce( 'updates' );
$_POST['plugin'] = '../foo/bar.php';
$_POST['slug'] = 'foo';
// Make the request
try {
$this->_handleAjax( 'update-plugin' );
} catch ( WPAjaxDieContinueException $e ) {
unset( $e );
}
// Get the response.
$response = json_decode( $this->_last_response, true );
$expected = array(
'success' => false,
'data' => array(
'update' => 'plugin',
'slug' => 'foo',
'errorMessage' => 'Sorry, you are not allowed to update plugins for this site.',
'oldVersion' => '',
'newVersion' => '',
),
);
$this->assertEqualSets( $expected, $response );
}
public function test_update_plugin() {
$this->_setRole( 'administrator' );
$_POST['_ajax_nonce'] = wp_create_nonce( 'updates' );
$_POST['plugin'] = 'hello.php';
$_POST['slug'] = 'hello-dolly';
// Make the request
try {
// Prevent wp_update_plugins() from running
wp_installing( true );
$this->_handleAjax( 'update-plugin' );
wp_installing( false );
} catch ( WPAjaxDieContinueException $e ) {
unset( $e );
}
// Get the response.
$response = json_decode( $this->_last_response, true );
$expected = array(
'success' => false,
'data' => array(
'update' => 'plugin',
'slug' => 'hello-dolly',
'plugin' => 'hello.php',
'pluginName' => 'Hello Dolly',
'errorMessage' => 'Plugin update failed.',
'oldVersion' => 'Version 1.6',
'newVersion' => '',
'debug' => array( 'The plugin is at the latest version.' ),
),
);
$this->assertEqualSets( $expected, $response );
}
}