Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ On a network the old screen keeps its row in the network admin, and the *Setting

## Setting up Reprint manually

`wp-admin/admin.php?page=wpcom-migration-manual` (`manage_options`, single-site only). Nothing links to it; support gives out the URL. It holds the export secret form, a *Remove secret* button (which also turns the exporter off), the exporter toggle, the export URL and the WordPress.com blog ID. Deactivating the plugin disconnects and discards the secret and the exporter state.
`wp-admin/admin.php?page=wpcom-migration-manual` (`administrator` role, single-site only). Nothing links to it; support gives out the URL. It holds the export secret form, a *Remove secret* button (which also turns the exporter off), the exporter toggle, the export URL and the WordPress.com blog ID. Deactivating the plugin disconnects and discards the secret and the exporter state.

## How WordPress.com provisions the exporter

Expand Down
6 changes: 3 additions & 3 deletions plugin/reprint/class-manual-page.php
Original file line number Diff line number Diff line change
Expand Up @@ -249,8 +249,8 @@ private function authorize( $action ) {
wp_die( esc_html__( 'The exporter is not supported on networks.', 'wpcom-migration' ) );
}

if ( ! current_user_can( 'manage_options' ) ) {
wp_die( esc_html__( 'You are not allowed to manage the exporter.', 'wpcom-migration' ) );
if ( ! in_array( 'administrator', wp_get_current_user()->roles, true ) ) {
wp_die( esc_html__( 'Only an administrator can manage the exporter.', 'wpcom-migration' ) );
}

check_admin_referer( $action );
Expand All @@ -272,7 +272,7 @@ private function redirect_with_notice( $result ) {
* once the secret is valid; the blog ID when connected.
*/
public function render_page() {
if ( ! current_user_can( 'manage_options' ) ) {
if ( ! in_array( 'administrator', wp_get_current_user()->roles, true ) ) {
return;
}

Expand Down
4 changes: 4 additions & 0 deletions tests/e2e/blueprint-screen.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@
"step": "runPHP",
"code": "<?php require '/wordpress/wp-load.php'; require '/wordpress/wp-content/wpcom-migration-e2e/screen-steps.php'; wpcom_migration_e2e_screen_step( 'assert-secret-saved' );"
},
{
"step": "runPHP",
"code": "<?php require '/wordpress/wp-load.php'; require '/wordpress/wp-content/wpcom-migration-e2e/screen-steps.php'; try { wpcom_migration_e2e_screen_step( 'reject-non-administrator-manual-access' ); } catch ( Throwable $error ) { echo $error->getMessage(); throw $error; }"
},
{
"step": "runPHP",
"code": "<?php require '/wordpress/wp-load.php'; require '/wordpress/wp-content/wpcom-migration-e2e/screen-steps.php'; wpcom_migration_e2e_screen_step( 'invalidate-secret' );"
Expand Down
68 changes: 68 additions & 0 deletions tests/e2e/screen-steps.php
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,74 @@ function wpcom_migration_e2e_screen_step( $step ) {
wpcom_migration_e2e_expect_contains( $manual_html, 'Remove secret', $step );
break;

case 'reject-non-administrator-manual-access':
add_role(
'migration-options-only',
'Migration options only',
array(
'read' => true,
'manage_options' => true,
)
);
$user_id = wp_insert_user(
array(
'user_login' => 'migration-options-only',
'user_pass' => wp_generate_password( 24, false ),
'role' => 'migration-options-only',
)
);
if ( is_wp_error( $user_id ) ) {
throw new RuntimeException( 'Could not create the options-only user: ' . $user_id->get_error_message() );
}

wp_set_current_user( $user_id );
if ( ! current_user_can( 'manage_options' ) || in_array( 'administrator', wp_get_current_user()->roles, true ) ) {
throw new RuntimeException( 'The test user must have manage_options without the administrator role.' );
}
if ( '' !== wpcom_migration_e2e_render_manual( $manual ) ) {
throw new RuntimeException( 'A non-administrator can view the manual export screen.' );
}

$die_handler = static function () {
throw new RuntimeException( 'manual access denied' );
};
$die_filter = static function () use ( $die_handler ) {
return $die_handler;
};
$redirect_filter = static function () {
throw new RuntimeException( 'A manual action reached its redirect.' );
};
add_filter( 'wp_die_handler', $die_filter );
add_filter( 'wp_redirect', $redirect_filter );
try {
$actions = array(
array( Manual_Page::SAVE_SECRET_ACTION, 'handle_save_secret', array( Manual_Page::SECRET_FIELD => 'attacker-secret' ) ),
array( Manual_Page::SAVE_ENABLED_ACTION, 'handle_save_enabled', array( Manual_Page::ENABLED_FIELD => '1' ) ),
array( Manual_Page::DISCARD_SECRET_ACTION, 'handle_discard_secret', array() ),
);
foreach ( $actions as $action ) {
wpcom_migration_e2e_post( $action[0], $action[2] );
try {
$manual->{$action[1]}();
throw new RuntimeException( 'A manual action returned without denying access.' );
} catch ( RuntimeException $exception ) {
if ( 'manual access denied' !== $exception->getMessage() ) {
throw $exception;
}
}
}
} finally {
remove_filter( 'wp_die_handler', $die_filter );
remove_filter( 'wp_redirect', $redirect_filter );
wp_set_current_user( 1 );
}

$state = Exporter::get_state();
if ( 'smoke-secret-0123456789abcdef0123456789abcdef' !== get_option( Exporter::SECRET_OPTION ) || ! $state['secret_valid'] || $state['window_open'] ) {
throw new RuntimeException( 'Non-administrator access changed exporter credentials or state.' );
}
break;

case 'invalidate-secret':
delete_option( Exporter::SECRET_HASH_OPTION );
$state = Exporter::get_state();
Expand Down
Loading