Options, Meta APIs: Require a confirmation link in an email to be clicked when an admin attempts to change the site admin email address.

This adds this previously Multisite-only functionality to single site installations too. This change prevents accidental or erroneous email address changes from potentially locking users out of their site.

Props MatheusGimenez, johnbillion

Fixes #39118

Built from https://develop.svn.wordpress.org/trunk@41254


git-svn-id: http://core.svn.wordpress.org/trunk@41094 1a063a9b-81f0-0310-95a4-ce76da25c4cd
This commit is contained in:
John Blackbourn 2017-08-14 20:13:43 +00:00
parent b51f60de6d
commit b52e37f9bf
7 changed files with 134 additions and 130 deletions

View File

@ -56,6 +56,9 @@ add_action( 'update_option_siteurl', 'update_home_siteurl', 10, 2 );
add_action( 'update_option_page_on_front', 'update_home_siteurl', 10, 2 ); add_action( 'update_option_page_on_front', 'update_home_siteurl', 10, 2 );
add_action( 'update_option_admin_email', 'wp_site_admin_email_change_notification', 10, 3 ); add_action( 'update_option_admin_email', 'wp_site_admin_email_change_notification', 10, 3 );
add_action( 'add_option_new_admin_email', 'update_option_new_admin_email', 10, 2 );
add_action( 'update_option_new_admin_email', 'update_option_new_admin_email', 10, 2 );
add_filter( 'heartbeat_received', 'wp_check_locked_posts', 10, 3 ); add_filter( 'heartbeat_received', 'wp_check_locked_posts', 10, 3 );
add_filter( 'heartbeat_received', 'wp_refresh_post_lock', 10, 3 ); add_filter( 'heartbeat_received', 'wp_refresh_post_lock', 10, 3 );
add_filter( 'wp_refresh_nonces', 'wp_refresh_post_nonces', 10, 3 ); add_filter( 'wp_refresh_nonces', 'wp_refresh_post_nonces', 10, 3 );

View File

@ -936,3 +936,83 @@ function wp_page_reload_on_back_button_js() {
</script> </script>
<?php <?php
} }
/**
* Send a confirmation request email when a change of site admin email address is attempted.
*
* The new site admin address will not become active until confirmed.
*
* @since 3.0.0
* @since 4.9.0 This function was moved from wp-admin/includes/ms.php so it's no longer Multisite specific.
*
* @param string $old_value The old site admin email address.
* @param string $value The proposed new site admin email address.
*/
function update_option_new_admin_email( $old_value, $value ) {
if ( $value == get_option( 'admin_email' ) || ! is_email( $value ) ) {
return;
}
$hash = md5( $value . time() . mt_rand() );
$new_admin_email = array(
'hash' => $hash,
'newemail' => $value,
);
update_option( 'adminhash', $new_admin_email );
$switched_locale = switch_to_locale( get_user_locale() );
/* translators: Do not translate USERNAME, ADMIN_URL, EMAIL, SITENAME, SITEURL: those are placeholders. */
$email_text = __( 'Howdy ###USERNAME###,
You recently requested to have the administration email address on
your site changed.
If this is correct, please click on the following link to change it:
###ADMIN_URL###
You can safely ignore and delete this email if you do not want to
take this action.
This email has been sent to ###EMAIL###
Regards,
All at ###SITENAME###
###SITEURL###' );
/**
* Filters the text of the email sent when a change of site admin email address is attempted.
*
* The following strings have a special meaning and will get replaced dynamically:
* ###USERNAME### The current user's username.
* ###ADMIN_URL### The link to click on to confirm the email change.
* ###EMAIL### The proposed new site admin email address.
* ###SITENAME### The name of the site.
* ###SITEURL### The URL to the site.
*
* @since MU (3.0.0)
* @since 4.9.0 This filter is no longer Multisite specific.
*
* @param string $email_text Text in the email.
* @param array $new_admin_email {
* Data relating to the new site admin email address.
*
* @type string $hash The secure hash used in the confirmation link URL.
* @type string $newemail The proposed new site admin email address.
* }
*/
$content = apply_filters( 'new_admin_email_content', $email_text, $new_admin_email );
$current_user = wp_get_current_user();
$content = str_replace( '###USERNAME###', $current_user->user_login, $content );
$content = str_replace( '###ADMIN_URL###', esc_url( self_admin_url( 'options.php?adminhash=' . $hash ) ), $content );
$content = str_replace( '###EMAIL###', $value, $content );
$content = str_replace( '###SITENAME###', wp_specialchars_decode( get_option( 'blogname' ), ENT_QUOTES ), $content );
$content = str_replace( '###SITEURL###', home_url(), $content );
wp_mail( $value, sprintf( __( '[%s] New Admin Email Address' ), wp_specialchars_decode( get_option( 'blogname' ), ENT_QUOTES ) ), $content );
if ( $switched_locale ) {
restore_previous_locale();
}
}

View File

@ -16,10 +16,6 @@ add_action( 'network_admin_notices', 'new_user_email_admin_notice' );
add_action( 'admin_page_access_denied', '_access_denied_splash', 99 ); add_action( 'admin_page_access_denied', '_access_denied_splash', 99 );
add_action( 'add_option_new_admin_email', 'update_option_new_admin_email', 10, 2 );
add_action( 'update_option_new_admin_email', 'update_option_new_admin_email', 10, 2 );
// Site Hooks. // Site Hooks.
add_action( 'wpmueditblogaction', 'upload_space_setting' ); add_action( 'wpmueditblogaction', 'upload_space_setting' );

View File

@ -265,83 +265,6 @@ function wpmu_delete_user( $id ) {
return true; return true;
} }
/**
* Send a confirmation request email when a change of site admin email address is attempted.
*
* The new site admin address will not become active until confirmed.
*
* @since 3.0.0
*
* @param string $old_value The old site admin email address.
* @param string $value The proposed new site admin email address.
*/
function update_option_new_admin_email( $old_value, $value ) {
if ( $value == get_option( 'admin_email' ) || !is_email( $value ) )
return;
$hash = md5( $value. time() .mt_rand() );
$new_admin_email = array(
'hash' => $hash,
'newemail' => $value
);
update_option( 'adminhash', $new_admin_email );
$switched_locale = switch_to_locale( get_user_locale() );
/* translators: Do not translate USERNAME, ADMIN_URL, EMAIL, SITENAME, SITEURL: those are placeholders. */
$email_text = __( 'Howdy ###USERNAME###,
You recently requested to have the administration email address on
your site changed.
If this is correct, please click on the following link to change it:
###ADMIN_URL###
You can safely ignore and delete this email if you do not want to
take this action.
This email has been sent to ###EMAIL###
Regards,
All at ###SITENAME###
###SITEURL###' );
/**
* Filters the text of the email sent when a change of site admin email address is attempted.
*
* The following strings have a special meaning and will get replaced dynamically:
* ###USERNAME### The current user's username.
* ###ADMIN_URL### The link to click on to confirm the email change.
* ###EMAIL### The proposed new site admin email address.
* ###SITENAME### The name of the site.
* ###SITEURL### The URL to the site.
*
* @since MU (3.0.0)
*
* @param string $email_text Text in the email.
* @param array $new_admin_email {
* Data relating to the new site admin email address.
*
* @type string $hash The secure hash used in the confirmation link URL.
* @type string $newemail The proposed new site admin email address.
* }
*/
$content = apply_filters( 'new_admin_email_content', $email_text, $new_admin_email );
$current_user = wp_get_current_user();
$content = str_replace( '###USERNAME###', $current_user->user_login, $content );
$content = str_replace( '###ADMIN_URL###', esc_url( self_admin_url( 'options.php?adminhash='.$hash ) ), $content );
$content = str_replace( '###EMAIL###', $value, $content );
$content = str_replace( '###SITENAME###', wp_specialchars_decode( get_site_option( 'site_name' ), ENT_QUOTES ), $content );
$content = str_replace( '###SITEURL###', network_home_url(), $content );
wp_mail( $value, sprintf( __( '[%s] New Admin Email Address' ), wp_specialchars_decode( get_option( 'blogname' ), ENT_QUOTES ) ), $content );
if ( $switched_locale ) {
restore_previous_locale();
}
}
/** /**
* Check whether a site has used its allotted upload space. * Check whether a site has used its allotted upload space.
* *

View File

@ -56,20 +56,25 @@ include( ABSPATH . 'wp-admin/admin-header.php' );
<?php settings_fields('general'); ?> <?php settings_fields('general'); ?>
<table class="form-table"> <table class="form-table">
<tr> <tr>
<th scope="row"><label for="blogname"><?php _e('Site Title') ?></label></th> <th scope="row"><label for="blogname"><?php _e('Site Title') ?></label></th>
<td><input name="blogname" type="text" id="blogname" value="<?php form_option('blogname'); ?>" class="regular-text" /></td> <td><input name="blogname" type="text" id="blogname" value="<?php form_option('blogname'); ?>" class="regular-text" /></td>
</tr> </tr>
<tr> <tr>
<th scope="row"><label for="blogdescription"><?php _e('Tagline') ?></label></th> <th scope="row"><label for="blogdescription"><?php _e('Tagline') ?></label></th>
<td><input name="blogdescription" type="text" id="blogdescription" aria-describedby="tagline-description" value="<?php form_option('blogdescription'); ?>" class="regular-text" /> <td><input name="blogdescription" type="text" id="blogdescription" aria-describedby="tagline-description" value="<?php form_option('blogdescription'); ?>" class="regular-text" />
<p class="description" id="tagline-description"><?php _e( 'In a few words, explain what this site is about.' ) ?></p></td> <p class="description" id="tagline-description"><?php _e( 'In a few words, explain what this site is about.' ) ?></p></td>
</tr> </tr>
<?php if ( !is_multisite() ) { ?> <?php if ( !is_multisite() ) { ?>
<tr> <tr>
<th scope="row"><label for="siteurl"><?php _e('WordPress Address (URL)') ?></label></th> <th scope="row"><label for="siteurl"><?php _e('WordPress Address (URL)') ?></label></th>
<td><input name="siteurl" type="url" id="siteurl" value="<?php form_option( 'siteurl' ); ?>"<?php disabled( defined( 'WP_SITEURL' ) ); ?> class="regular-text code<?php if ( defined( 'WP_SITEURL' ) ) echo ' disabled' ?>" /></td> <td><input name="siteurl" type="url" id="siteurl" value="<?php form_option( 'siteurl' ); ?>"<?php disabled( defined( 'WP_SITEURL' ) ); ?> class="regular-text code<?php if ( defined( 'WP_SITEURL' ) ) echo ' disabled' ?>" /></td>
</tr> </tr>
<tr> <tr>
<th scope="row"><label for="home"><?php _e('Site Address (URL)') ?></label></th> <th scope="row"><label for="home"><?php _e('Site Address (URL)') ?></label></th>
<td><input name="home" type="url" id="home" aria-describedby="home-description" value="<?php form_option( 'home' ); ?>"<?php disabled( defined( 'WP_HOME' ) ); ?> class="regular-text code<?php if ( defined( 'WP_HOME' ) ) echo ' disabled' ?>" /> <td><input name="home" type="url" id="home" aria-describedby="home-description" value="<?php form_option( 'home' ); ?>"<?php disabled( defined( 'WP_HOME' ) ); ?> class="regular-text code<?php if ( defined( 'WP_HOME' ) ) echo ' disabled' ?>" />
@ -77,34 +82,18 @@ include( ABSPATH . 'wp-admin/admin-header.php' );
<p class="description" id="home-description"><?php _e( 'Enter the address here if you <a href="https://codex.wordpress.org/Giving_WordPress_Its_Own_Directory">want your site home page to be different from your WordPress installation directory.</a>' ); ?></p></td> <p class="description" id="home-description"><?php _e( 'Enter the address here if you <a href="https://codex.wordpress.org/Giving_WordPress_Its_Own_Directory">want your site home page to be different from your WordPress installation directory.</a>' ); ?></p></td>
<?php endif; ?> <?php endif; ?>
</tr> </tr>
<?php } ?>
<tr> <tr>
<th scope="row"><label for="admin_email"><?php _e('Email Address') ?> </label></th> <th scope="row"><label for="new_admin_email"><?php _e( 'Email Address' ); ?></label></th>
<td><input name="admin_email" type="email" id="admin_email" aria-describedby="admin-email-description" value="<?php form_option( 'admin_email' ); ?>" class="regular-text ltr" />
<p class="description" id="admin-email-description"><?php _e( 'This address is used for admin purposes, like new user notification.' ) ?></p></td>
</tr>
<tr>
<th scope="row"><?php _e('Membership') ?></th>
<td> <fieldset><legend class="screen-reader-text"><span><?php _e('Membership') ?></span></legend><label for="users_can_register">
<input name="users_can_register" type="checkbox" id="users_can_register" value="1" <?php checked('1', get_option('users_can_register')); ?> />
<?php _e('Anyone can register') ?></label>
</fieldset></td>
</tr>
<tr>
<th scope="row"><label for="default_role"><?php _e('New User Default Role') ?></label></th>
<td>
<select name="default_role" id="default_role"><?php wp_dropdown_roles( get_option('default_role') ); ?></select>
</td>
</tr>
<?php } else { ?>
<tr>
<th scope="row"><label for="new_admin_email"><?php _e('Email Address') ?> </label></th>
<td><input name="new_admin_email" type="email" id="new_admin_email" aria-describedby="new-admin-email-description" value="<?php form_option( 'admin_email' ); ?>" class="regular-text ltr" /> <td><input name="new_admin_email" type="email" id="new_admin_email" aria-describedby="new-admin-email-description" value="<?php form_option( 'admin_email' ); ?>" class="regular-text ltr" />
<p class="description" id="new-admin-email-description"><?php _e( 'This address is used for admin purposes. If you change this we will send you an email at your new address to confirm it. <strong>The new address will not become active until confirmed.</strong>' ) ?></p> <p class="description" id="new-admin-email-description"><?php _e( 'This address is used for admin purposes. If you change this we will send you an email at your new address to confirm it. <strong>The new address will not become active until confirmed.</strong>' ); ?></p>
<?php <?php
$new_admin_email = get_option( 'new_admin_email' ); $new_admin_email = get_option( 'new_admin_email' );
if ( $new_admin_email && $new_admin_email != get_option('admin_email') ) : ?> if ( $new_admin_email && $new_admin_email != get_option( 'admin_email' ) ) : ?>
<div class="updated inline"> <div class="updated inline">
<p><?php <p><?php
printf( printf(
/* translators: %s: new admin email */ /* translators: %s: new admin email */
__( 'There is a pending change of the admin email to %s.' ), __( 'There is a pending change of the admin email to %s.' ),
@ -115,11 +104,29 @@ if ( $new_admin_email && $new_admin_email != get_option('admin_email') ) : ?>
esc_url( wp_nonce_url( admin_url( 'options.php?dismiss=new_admin_email' ), 'dismiss-' . get_current_blog_id() . '-new_admin_email' ) ), esc_url( wp_nonce_url( admin_url( 'options.php?dismiss=new_admin_email' ), 'dismiss-' . get_current_blog_id() . '-new_admin_email' ) ),
__( 'Cancel' ) __( 'Cancel' )
); );
?></p> ?></p>
</div> </div>
<?php endif; ?> <?php endif; ?>
</td> </td>
</tr> </tr>
<?php if ( ! is_multisite() ) { ?>
<tr>
<th scope="row"><?php _e('Membership') ?></th>
<td> <fieldset><legend class="screen-reader-text"><span><?php _e('Membership') ?></span></legend><label for="users_can_register">
<input name="users_can_register" type="checkbox" id="users_can_register" value="1" <?php checked('1', get_option('users_can_register')); ?> />
<?php _e('Anyone can register') ?></label>
</fieldset></td>
</tr>
<tr>
<th scope="row"><label for="default_role"><?php _e('New User Default Role') ?></label></th>
<td>
<select name="default_role" id="default_role"><?php wp_dropdown_roles( get_option('default_role') ); ?></select>
</td>
</tr>
<?php } <?php }
$languages = get_available_languages(); $languages = get_available_languages();

View File

@ -53,11 +53,10 @@ if ( ! current_user_can( $capability ) ) {
} }
// Handle admin email change requests // Handle admin email change requests
if ( is_multisite() ) { if ( ! empty( $_GET[ 'adminhash' ] ) ) {
if ( ! empty($_GET[ 'adminhash' ] ) ) {
$new_admin_details = get_option( 'adminhash' ); $new_admin_details = get_option( 'adminhash' );
$redirect = 'options-general.php?updated=false'; $redirect = 'options-general.php?updated=false';
if ( is_array( $new_admin_details ) && hash_equals( $new_admin_details[ 'hash' ], $_GET[ 'adminhash' ] ) && !empty($new_admin_details[ 'newemail' ]) ) { if ( is_array( $new_admin_details ) && hash_equals( $new_admin_details[ 'hash' ], $_GET[ 'adminhash' ] ) && ! empty( $new_admin_details[ 'newemail' ] ) ) {
update_option( 'admin_email', $new_admin_details[ 'newemail' ] ); update_option( 'admin_email', $new_admin_details[ 'newemail' ] );
delete_option( 'adminhash' ); delete_option( 'adminhash' );
delete_option( 'new_admin_email' ); delete_option( 'new_admin_email' );
@ -65,13 +64,12 @@ if ( is_multisite() ) {
} }
wp_redirect( admin_url( $redirect ) ); wp_redirect( admin_url( $redirect ) );
exit; exit;
} elseif ( ! empty( $_GET['dismiss'] ) && 'new_admin_email' == $_GET['dismiss'] ) { } elseif ( ! empty( $_GET['dismiss'] ) && 'new_admin_email' == $_GET['dismiss'] ) {
check_admin_referer( 'dismiss-' . get_current_blog_id() . '-new_admin_email' ); check_admin_referer( 'dismiss-' . get_current_blog_id() . '-new_admin_email' );
delete_option( 'adminhash' ); delete_option( 'adminhash' );
delete_option( 'new_admin_email' ); delete_option( 'new_admin_email' );
wp_redirect( admin_url( 'options-general.php?updated=true' ) ); wp_redirect( admin_url( 'options-general.php?updated=true' ) );
exit; exit;
}
} }
if ( is_multisite() && ! current_user_can( 'manage_network_options' ) && 'update' != $action ) { if ( is_multisite() && ! current_user_can( 'manage_network_options' ) && 'update' != $action ) {
@ -83,7 +81,7 @@ if ( is_multisite() && ! current_user_can( 'manage_network_options' ) && 'update
} }
$whitelist_options = array( $whitelist_options = array(
'general' => array( 'blogname', 'blogdescription', 'gmt_offset', 'date_format', 'time_format', 'start_of_week', 'timezone_string', 'WPLANG' ), 'general' => array( 'blogname', 'blogdescription', 'gmt_offset', 'date_format', 'time_format', 'start_of_week', 'timezone_string', 'WPLANG', 'new_admin_email' ),
'discussion' => array( 'default_pingback_flag', 'default_ping_status', 'default_comment_status', 'comments_notify', 'moderation_notify', 'comment_moderation', 'require_name_email', 'comment_whitelist', 'comment_max_links', 'moderation_keys', 'blacklist_keys', 'show_avatars', 'avatar_rating', 'avatar_default', 'close_comments_for_old_posts', 'close_comments_days_old', 'thread_comments', 'thread_comments_depth', 'page_comments', 'comments_per_page', 'default_comments_page', 'comment_order', 'comment_registration' ), 'discussion' => array( 'default_pingback_flag', 'default_ping_status', 'default_comment_status', 'comments_notify', 'moderation_notify', 'comment_moderation', 'require_name_email', 'comment_whitelist', 'comment_max_links', 'moderation_keys', 'blacklist_keys', 'show_avatars', 'avatar_rating', 'avatar_default', 'close_comments_for_old_posts', 'close_comments_days_old', 'thread_comments', 'thread_comments_depth', 'page_comments', 'comments_per_page', 'default_comments_page', 'comment_order', 'comment_registration' ),
'media' => array( 'thumbnail_size_w', 'thumbnail_size_h', 'thumbnail_crop', 'medium_size_w', 'medium_size_h', 'large_size_w', 'large_size_h', 'image_default_size', 'image_default_align', 'image_default_link_type' ), 'media' => array( 'thumbnail_size_w', 'thumbnail_size_h', 'thumbnail_crop', 'medium_size_w', 'medium_size_h', 'large_size_w', 'large_size_h', 'image_default_size', 'image_default_align', 'image_default_link_type' ),
'reading' => array( 'posts_per_page', 'posts_per_rss', 'rss_use_excerpt', 'show_on_front', 'page_on_front', 'page_for_posts', 'blog_public' ), 'reading' => array( 'posts_per_page', 'posts_per_rss', 'rss_use_excerpt', 'show_on_front', 'page_on_front', 'page_for_posts', 'blog_public' ),
@ -107,7 +105,6 @@ if ( !is_multisite() ) {
if ( !defined( 'WP_HOME' ) ) if ( !defined( 'WP_HOME' ) )
$whitelist_options['general'][] = 'home'; $whitelist_options['general'][] = 'home';
$whitelist_options['general'][] = 'admin_email';
$whitelist_options['general'][] = 'users_can_register'; $whitelist_options['general'][] = 'users_can_register';
$whitelist_options['general'][] = 'default_role'; $whitelist_options['general'][] = 'default_role';
@ -122,8 +119,6 @@ if ( !is_multisite() ) {
$whitelist_options['media'][] = 'upload_url_path'; $whitelist_options['media'][] = 'upload_url_path';
} }
} else { } else {
$whitelist_options['general'][] = 'new_admin_email';
/** /**
* Filters whether the post-by-email functionality is enabled. * Filters whether the post-by-email functionality is enabled.
* *

View File

@ -4,7 +4,7 @@
* *
* @global string $wp_version * @global string $wp_version
*/ */
$wp_version = '4.9-alpha-41253'; $wp_version = '4.9-alpha-41254';
/** /**
* Holds the WordPress DB revision, increments when changes are made to the WordPress DB schema. * Holds the WordPress DB revision, increments when changes are made to the WordPress DB schema.