namespace, '/' . $this->rest_base . '/rotate-export-secret', array( array( 'methods' => WP_REST_Server::CREATABLE, 'callback' => array( $this, 'rotate_secret' ), 'permission_callback' => array( $this, 'permission_check' ), ), ) ); register_rest_route( $this->namespace, '/' . $this->rest_base . '/enable-export', array( array( 'methods' => WP_REST_Server::CREATABLE, 'callback' => array( $this, 'enable_export' ), 'permission_callback' => array( $this, 'permission_check' ), ), ) ); } /** * Opens the export window without rotating the secret, so a client that * already has one can reopen a window that has closed. * * Registered only where the feature is available, so clients also use a 404 * from here to tell whether the site supports export at all. * * @return WP_REST_Response The unix timestamp the window was opened at. */ public function enable_export() { $enabled_at = Reprint_Exporter::open_export_window(); Reprint_Exporter::record_event( 'window_opened', array( 'user_id' => get_current_user_id() ) ); return new WP_REST_Response( array( 'enabled_at' => $enabled_at ), 200 ); } /** * Rotates the shared secret and returns it. * * Uses random_bytes() rather than wp_generate_password(). That helper is for * passwords a person reads and types, and sites can filter it through * `random_password` to enforce their own policy — an extension point we do * not want on a credential. random_bytes() also throws rather than quietly * falling back to a weaker source, which wp_rand() will do. * * @return WP_REST_Response The new secret on success, or a 500 error. */ public function rotate_secret() { $secret = bin2hex( random_bytes( 32 ) ); if ( ! Reprint_Exporter::store_secret( $secret ) ) { return new WP_REST_Response( array( 'error' => 'Failed to persist the new secret.' ), 500 ); } Reprint_Exporter::record_event( 'secret_rotated', array( 'user_id' => get_current_user_id() ) ); return new WP_REST_Response( array( 'secret' => $secret ), 200 ); } /** * Permission callback: a Jetpack-signed request from a site administrator. * * Deliberately a role check, not a capability one. This hands out a secret * that streams the whole database and file tree, and no capability says * that — `manage_options` is the closest, but plugins grant it to shop * managers and the like. * * @return bool */ public function permission_check() { if ( ! Rest_Authentication::is_signed_with_user_token() ) { return false; } $user = wp_get_current_user(); if ( ! $user || ! $user->exists() ) { return false; } // Network administrator only: the export takes every table and everything // under ABSPATH, so a subsite administrator would leave with every other // site's users, content and uploads. if ( is_multisite() ) { return is_super_admin( $user->ID ); } return in_array( 'administrator', $user->roles, true ); } }