<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head><meta http-equiv="content-type" content="text/html; charset=utf-8" />
<title>[43085] trunk: Privacy: Limit export and erasure to super admins on Multisite.</title>
</head>
<body>

<style type="text/css"><!--
#msg dl.meta { border: 1px #006 solid; background: #369; padding: 6px; color: #fff; }
#msg dl.meta dt { float: left; width: 6em; font-weight: bold; }
#msg dt:after { content:':';}
#msg dl, #msg dt, #msg ul, #msg li, #header, #footer, #logmsg { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt;  }
#msg dl a { font-weight: bold}
#msg dl a:link    { color:#fc3; }
#msg dl a:active  { color:#ff0; }
#msg dl a:visited { color:#cc6; }
h3 { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt; font-weight: bold; }
#msg pre { white-space: pre-line; overflow: auto; background: #ffc; border: 1px #fa0 solid; padding: 6px; }
#logmsg { background: #ffc; border: 1px #fa0 solid; padding: 1em 1em 0 1em; }
#logmsg p, #logmsg pre, #logmsg blockquote { margin: 0 0 1em 0; }
#logmsg p, #logmsg li, #logmsg dt, #logmsg dd { line-height: 14pt; }
#logmsg h1, #logmsg h2, #logmsg h3, #logmsg h4, #logmsg h5, #logmsg h6 { margin: .5em 0; }
#logmsg h1:first-child, #logmsg h2:first-child, #logmsg h3:first-child, #logmsg h4:first-child, #logmsg h5:first-child, #logmsg h6:first-child { margin-top: 0; }
#logmsg ul, #logmsg ol { padding: 0; list-style-position: inside; margin: 0 0 0 1em; }
#logmsg ul { text-indent: -1em; padding-left: 1em; }#logmsg ol { text-indent: -1.5em; padding-left: 1.5em; }
#logmsg > ul, #logmsg > ol { margin: 0 0 1em 0; }
#logmsg pre { background: #eee; padding: 1em; }
#logmsg blockquote { border: 1px solid #fa0; border-left-width: 10px; padding: 1em 1em 0 1em; background: white;}
#logmsg dl { margin: 0; }
#logmsg dt { font-weight: bold; }
#logmsg dd { margin: 0; padding: 0 0 0.5em 0; }
#logmsg dd:before { content:'\00bb';}
#logmsg table { border-spacing: 0px; border-collapse: collapse; border-top: 4px solid #fa0; border-bottom: 1px solid #fa0; background: #fff; }
#logmsg table th { text-align: left; font-weight: normal; padding: 0.2em 0.5em; border-top: 1px dotted #fa0; }
#logmsg table td { text-align: right; border-top: 1px dotted #fa0; padding: 0.2em 0.5em; }
#logmsg table thead th { text-align: center; border-bottom: 1px solid #fa0; }
#logmsg table th.Corner { text-align: left; }
#logmsg hr { border: none 0; border-top: 2px dashed #fa0; height: 1px; }
#header, #footer { color: #fff; background: #636; border: 1px #300 solid; padding: 6px; }
#patch { width: 100%; }
#patch h4 {font-family: verdana,arial,helvetica,sans-serif;font-size:10pt;padding:8px;background:#369;color:#fff;margin:0;}
#patch .propset h4, #patch .binary h4 {margin:0;}
#patch pre {padding:0;line-height:1.2em;margin:0;}
#patch .diff {width:100%;background:#eee;padding: 0 0 10px 0;overflow:auto;}
#patch .propset .diff, #patch .binary .diff  {padding:10px 0;}
#patch span {display:block;padding:0 10px;}
#patch .modfile, #patch .addfile, #patch .delfile, #patch .propset, #patch .binary, #patch .copfile {border:1px solid #ccc;margin:10px 0;}
#patch ins {background:#dfd;text-decoration:none;display:block;padding:0 10px;}
#patch del {background:#fdd;text-decoration:none;display:block;padding:0 10px;}
#patch .lines, .info {color:#888;background:#fff;}
--></style>
<div id="msg">
<dl class="meta" style="font-size: 105%">
<dt style="float: left; width: 6em; font-weight: bold">Revision</dt> <dd><a style="font-weight: bold" href="https://core.trac.wordpress.org/changeset/43085">43085</a><script type="application/ld+json">{"@context":"http://schema.org","@type":"EmailMessage","description":"Review this Commit","action":{"@type":"ViewAction","url":"https://core.trac.wordpress.org/changeset/43085","name":"Review Commit"}}</script></dd>
<dt style="float: left; width: 6em; font-weight: bold">Author</dt> <dd>iandunn</dd>
<dt style="float: left; width: 6em; font-weight: bold">Date</dt> <dd>2018-05-02 01:07:00 +0000 (Wed, 02 May 2018)</dd>
</dl>

<pre style='padding-left: 1em; margin: 2em 0; border-left: 2px solid #ccc; line-height: 1.25; font-size: 105%; font-family: sans-serif'>Privacy: Limit export and erasure to super admins on Multisite.

Multisite networks have a variety of use cases, and in many of them single-site administrators are not trusted to take actions that affect the whole network, require making decisions about legal compliance, etc. By default, those actions should require super admin capabilities. Plugins can be used to override that behavior if a particular site's use case calls for it.

Props allendav, jeremyfelt, iandunn.
Fixes <a href="https://core.trac.wordpress.org/ticket/43919">#43919</a>.</pre>

<h3>Modified Paths</h3>
<ul>
<li><a href="#trunksrcwpadminincludesajaxactionsphp">trunk/src/wp-admin/includes/ajax-actions.php</a></li>
<li><a href="#trunksrcwpadminincludesuserphp">trunk/src/wp-admin/includes/user.php</a></li>
<li><a href="#trunksrcwpincludescapabilitiesphp">trunk/src/wp-includes/capabilities.php</a></li>
<li><a href="#trunktestsphpunittestsusercapabilitiesphp">trunk/tests/phpunit/tests/user/capabilities.php</a></li>
</ul>

</div>
<div id="patch">
<h3>Diff</h3>
<a id="trunksrcwpadminincludesajaxactionsphp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/src/wp-admin/includes/ajax-actions.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/src/wp-admin/includes/ajax-actions.php      2018-05-02 01:03:53 UTC (rev 43084)
+++ trunk/src/wp-admin/includes/ajax-actions.php        2018-05-02 01:07:00 UTC (rev 43085)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -4344,7 +4344,7 @@
</span><span class="cx" style="display: block; padding: 0 10px">                wp_send_json_error( __( 'Invalid request ID.' ) );
</span><span class="cx" style="display: block; padding: 0 10px">        }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-        if ( ! current_user_can( 'manage_options' ) ) {
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ if ( ! current_user_can( 'export_others_personal_data' ) ) {
</ins><span class="cx" style="display: block; padding: 0 10px">                 wp_send_json_error( __( 'Invalid request.' ) );
</span><span class="cx" style="display: block; padding: 0 10px">        }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -4522,7 +4522,8 @@
</span><span class="cx" style="display: block; padding: 0 10px">                wp_send_json_error( __( 'Invalid request ID.' ) );
</span><span class="cx" style="display: block; padding: 0 10px">        }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-        if ( ! current_user_can( 'delete_users' ) ) {
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ // Both capabilities are required to avoid confusion, see `_wp_personal_data_removal_page()`.
+       if ( ! current_user_can( 'erase_others_personal_data' ) || ! current_user_can( 'delete_users' ) ) {
</ins><span class="cx" style="display: block; padding: 0 10px">                 wp_send_json_error( __( 'Invalid request.' ) );
</span><span class="cx" style="display: block; padding: 0 10px">        }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span></span></pre></div>
<a id="trunksrcwpadminincludesuserphp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/src/wp-admin/includes/user.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/src/wp-admin/includes/user.php      2018-05-02 01:03:53 UTC (rev 43084)
+++ trunk/src/wp-admin/includes/user.php        2018-05-02 01:07:00 UTC (rev 43085)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -785,8 +785,8 @@
</span><span class="cx" style="display: block; padding: 0 10px">  * @access private
</span><span class="cx" style="display: block; padding: 0 10px">  */
</span><span class="cx" style="display: block; padding: 0 10px"> function _wp_personal_data_export_page() {
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-        if ( ! current_user_can( 'manage_options' ) ) {
-               wp_die( esc_html__( 'Sorry, you are not allowed to manage privacy on this site.' ) );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ if ( ! current_user_can( 'export_others_personal_data' ) ) {
+               wp_die( __( 'Sorry, you are not allowed to export personal data on this site.' ) );
</ins><span class="cx" style="display: block; padding: 0 10px">         }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">        _wp_personal_data_handle_actions();
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -850,8 +850,14 @@
</span><span class="cx" style="display: block; padding: 0 10px">  * @access private
</span><span class="cx" style="display: block; padding: 0 10px">  */
</span><span class="cx" style="display: block; padding: 0 10px"> function _wp_personal_data_removal_page() {
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-        if ( ! current_user_can( 'delete_users' ) ) {
-               wp_die( esc_html__( 'Sorry, you are not allowed to manage privacy on this site.' ) );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ /*
+        * Require both caps in order to make it explicitly clear that delegating
+        * erasure from network admins to single-site admins will give them the
+        * ability to affect global users, rather than being limited to the site
+        * that they administer.
+        */
+       if ( ! current_user_can( 'erase_others_personal_data' ) || ! current_user_can( 'delete_users' ) ) {
+               wp_die( __( 'Sorry, you are not allowed to erase data on this site.' ) );
</ins><span class="cx" style="display: block; padding: 0 10px">         }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">        _wp_personal_data_handle_actions();
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -917,8 +923,8 @@
</span><span class="cx" style="display: block; padding: 0 10px">  * @access private
</span><span class="cx" style="display: block; padding: 0 10px">  */
</span><span class="cx" style="display: block; padding: 0 10px"> function _wp_privacy_hook_requests_page() {
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-        add_submenu_page( 'tools.php', __( 'Export Personal Data' ), __( 'Export Personal Data' ), 'manage_options', 'export_personal_data', '_wp_personal_data_export_page' );
-       add_submenu_page( 'tools.php', __( 'Remove Personal Data' ), __( 'Remove Personal Data' ), 'manage_options', 'remove_personal_data', '_wp_personal_data_removal_page' );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ add_submenu_page( 'tools.php', __( 'Export Personal Data' ), __( 'Export Personal Data' ), 'export_others_personal_data', 'export_personal_data', '_wp_personal_data_export_page' );
+       add_submenu_page( 'tools.php', __( 'Remove Personal Data' ), __( 'Remove Personal Data' ), 'erase_others_personal_data', 'remove_personal_data', '_wp_personal_data_removal_page' );
</ins><span class="cx" style="display: block; padding: 0 10px"> }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px"> // TODO: move the following classes in new files.
</span></span></pre></div>
<a id="trunksrcwpincludescapabilitiesphp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/src/wp-includes/capabilities.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/src/wp-includes/capabilities.php    2018-05-02 01:03:53 UTC (rev 43084)
+++ trunk/src/wp-includes/capabilities.php      2018-05-02 01:07:00 UTC (rev 43085)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -555,6 +555,10 @@
</span><span class="cx" style="display: block; padding: 0 10px">                                $caps[] = 'update_core';
</span><span class="cx" style="display: block; padding: 0 10px">                        }
</span><span class="cx" style="display: block; padding: 0 10px">                        break;
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                case 'export_others_personal_data':
+               case 'erase_others_personal_data':
+                       $caps[] = is_multisite() ? 'manage_network' : 'manage_options';
+                       break;
</ins><span class="cx" style="display: block; padding: 0 10px">                 default:
</span><span class="cx" style="display: block; padding: 0 10px">                        // Handle meta capabilities for custom post types.
</span><span class="cx" style="display: block; padding: 0 10px">                        global $post_type_meta_caps;
</span></span></pre></div>
<a id="trunktestsphpunittestsusercapabilitiesphp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/tests/phpunit/tests/user/capabilities.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/tests/phpunit/tests/user/capabilities.php   2018-05-02 01:03:53 UTC (rev 43084)
+++ trunk/tests/phpunit/tests/user/capabilities.php     2018-05-02 01:07:00 UTC (rev 43085)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -237,6 +237,8 @@
</span><span class="cx" style="display: block; padding: 0 10px">                        'update_languages'       => array( 'administrator' ),
</span><span class="cx" style="display: block; padding: 0 10px">                        'deactivate_plugins'     => array( 'administrator' ),
</span><span class="cx" style="display: block; padding: 0 10px">                        'upgrade_php'            => array( 'administrator' ),
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                        'export_others_personal_data' => array( 'administrator' ),
+                       'erase_others_personal_data'  => array( 'administrator' ),
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                        'edit_categories'        => array( 'administrator', 'editor' ),
</span><span class="cx" style="display: block; padding: 0 10px">                        'delete_categories'      => array( 'administrator', 'editor' ),
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -269,6 +271,8 @@
</span><span class="cx" style="display: block; padding: 0 10px">                        'update_languages'       => array(),
</span><span class="cx" style="display: block; padding: 0 10px">                        'deactivate_plugins'     => array(),
</span><span class="cx" style="display: block; padding: 0 10px">                        'upgrade_php'            => array(),
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                        'export_others_personal_data' => array( '' ),
+                       'erase_others_personal_data'  => array( '' ),
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                        'customize'              => array( 'administrator' ),
</span><span class="cx" style="display: block; padding: 0 10px">                        'delete_site'            => array( 'administrator' ),
</span></span></pre>
</div>
</div>

</body>
</html>