Brak opisu

class.jetpack-provision.php 9.7KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315
  1. <?php // phpcs:ignore WordPress.Files.FileName.InvalidClassFileName
  2. /**
  3. * Class file for provisioning Jetpack.
  4. *
  5. * @package automattic/jetpack
  6. */
  7. use Automattic\Jetpack\Connection\Client;
  8. use Automattic\Jetpack\Connection\Secrets;
  9. use Automattic\Jetpack\Connection\Tokens;
  10. use Automattic\Jetpack\Identity_Crisis;
  11. use Automattic\Jetpack\Roles;
  12. use Automattic\Jetpack\Sync\Actions;
  13. /**
  14. * Jetpack_Provision class.
  15. */
  16. class Jetpack_Provision {
  17. /**
  18. * Responsible for checking pre-conditions, registering site, and returning an array of details
  19. * that can be used to provision a plan for the site.
  20. *
  21. * @param array $named_args The array of arguments.
  22. *
  23. * @return WP_Error|array
  24. */
  25. public static function register_and_build_request_body( $named_args ) {
  26. $url_args = array(
  27. 'home_url' => 'WP_HOME',
  28. 'site_url' => 'WP_SITEURL',
  29. );
  30. foreach ( $url_args as $url_arg => $constant_name ) {
  31. if ( isset( $named_args[ $url_arg ] ) ) {
  32. add_filter(
  33. $url_arg,
  34. function () use ( $url_arg, $named_args ) {
  35. return $named_args[ $url_arg ];
  36. },
  37. 11
  38. );
  39. }
  40. }
  41. // If Jetpack is currently connected, and is not in Safe Mode already, kick off a sync of the current
  42. // functions/callables so that we can test if this site is in IDC.
  43. if ( Jetpack::is_connection_ready() && ! Identity_Crisis::validate_sync_error_idc_option() && Actions::sync_allowed() ) {
  44. Actions::do_full_sync( array( 'functions' => true ) );
  45. Actions::$sender->do_full_sync();
  46. }
  47. if ( Identity_Crisis::validate_sync_error_idc_option() ) {
  48. return new WP_Error(
  49. 'site_in_safe_mode',
  50. __( 'Can not provision a plan while in safe mode. See: https://jetpack.com/support/safe-mode/', 'jetpack' )
  51. );
  52. }
  53. if ( ! Jetpack::connection()->is_connected() || ( isset( $named_args['force_register'] ) && (int) $named_args['force_register'] ) ) {
  54. // This code mostly copied from Jetpack::admin_page_load.
  55. Jetpack::maybe_set_version_option();
  56. Jetpack::connection()->add_register_request_param( 'from', 'jetpack-start' );
  57. $registered = Jetpack::connection()->try_registration();
  58. if ( is_wp_error( $registered ) ) {
  59. return $registered;
  60. } elseif ( ! $registered ) {
  61. return new WP_Error( 'registration_error', __( 'There was an unspecified error registering the site', 'jetpack' ) );
  62. }
  63. }
  64. // If the user isn't specified, but we have a current master user, then set that to current user.
  65. $master_user_id = Jetpack_Options::get_option( 'master_user' );
  66. if ( ! get_current_user_id() && $master_user_id ) {
  67. wp_set_current_user( $master_user_id );
  68. }
  69. $site_icon = get_site_icon_url();
  70. $auto_enable_sso = ( ! Jetpack::connection()->has_connected_owner() || Jetpack::is_module_active( 'sso' ) );
  71. /** This filter is documented in class.jetpack-cli.php */
  72. if ( apply_filters( 'jetpack_start_enable_sso', $auto_enable_sso ) ) {
  73. $redirect_uri = add_query_arg(
  74. array(
  75. 'action' => 'jetpack-sso',
  76. 'redirect_to' => rawurlencode( admin_url() ),
  77. ),
  78. wp_login_url() // TODO: come back to Jetpack dashboard?
  79. );
  80. } else {
  81. $redirect_uri = admin_url();
  82. }
  83. $request_body = array(
  84. 'jp_version' => JETPACK__VERSION,
  85. 'redirect_uri' => $redirect_uri,
  86. );
  87. if ( $site_icon ) {
  88. $request_body['site_icon'] = $site_icon;
  89. }
  90. if ( get_current_user_id() ) {
  91. $user = wp_get_current_user();
  92. // Role.
  93. $roles = new Roles();
  94. $role = $roles->translate_current_user_to_role();
  95. $signed_role = Jetpack::connection()->sign_role( $role );
  96. $secrets = ( new Secrets() )->generate( 'authorize' );
  97. // Jetpack auth stuff.
  98. $request_body['scope'] = $signed_role;
  99. $request_body['secret'] = $secrets['secret_1'];
  100. // User stuff.
  101. $request_body['user_id'] = $user->ID;
  102. $request_body['user_email'] = $user->user_email;
  103. $request_body['user_login'] = $user->user_login;
  104. }
  105. // Optional additional params.
  106. if ( isset( $named_args['wpcom_user_id'] ) && ! empty( $named_args['wpcom_user_id'] ) ) {
  107. $request_body['wpcom_user_id'] = $named_args['wpcom_user_id'];
  108. }
  109. // Override email of selected user.
  110. if ( isset( $named_args['wpcom_user_email'] ) && ! empty( $named_args['wpcom_user_email'] ) ) {
  111. $request_body['user_email'] = $named_args['wpcom_user_email'];
  112. }
  113. if ( isset( $named_args['plan'] ) && ! empty( $named_args['plan'] ) ) {
  114. $request_body['plan'] = $named_args['plan'];
  115. }
  116. if ( isset( $named_args['onboarding'] ) && ! empty( $named_args['onboarding'] ) ) {
  117. $request_body['onboarding'] = (int) $named_args['onboarding'];
  118. }
  119. if ( isset( $named_args['force_connect'] ) && ! empty( $named_args['force_connect'] ) ) {
  120. $request_body['force_connect'] = (int) $named_args['force_connect'];
  121. }
  122. if ( isset( $request_body['onboarding'] ) && (bool) $request_body['onboarding'] ) {
  123. Jetpack::create_onboarding_token();
  124. }
  125. return $request_body;
  126. }
  127. /**
  128. * Given an access token and an array of arguments, will provision a plan for this site.
  129. *
  130. * @param string $access_token The access token from the partner.
  131. * @param array $named_args The arguments used for registering the site and then provisioning a plan.
  132. *
  133. * @return WP_Error|array
  134. */
  135. public static function partner_provision( $access_token, $named_args ) {
  136. // First, verify the token.
  137. $verify_response = self::verify_token( $access_token );
  138. if ( is_wp_error( $verify_response ) ) {
  139. return $verify_response;
  140. }
  141. $request_body = self::register_and_build_request_body( $named_args );
  142. if ( is_wp_error( $request_body ) ) {
  143. return $request_body;
  144. }
  145. $request = array(
  146. 'headers' => array(
  147. 'Authorization' => "Bearer $access_token",
  148. 'Host' => 'public-api.wordpress.com',
  149. ),
  150. 'timeout' => 60,
  151. 'method' => 'POST',
  152. 'body' => wp_json_encode( $request_body ),
  153. );
  154. $blog_id = Jetpack_Options::get_option( 'id' );
  155. $url = esc_url_raw(
  156. sprintf(
  157. '%s/rest/v1.3/jpphp/%d/partner-provision',
  158. self::get_api_host(),
  159. $blog_id
  160. )
  161. );
  162. if ( ! empty( $named_args['partner_tracking_id'] ) ) {
  163. $url = esc_url_raw( add_query_arg( 'partner_tracking_id', $named_args['partner_tracking_id'], $url ) );
  164. }
  165. // Add calypso env if set.
  166. $calypso_env = Jetpack::get_calypso_env();
  167. if ( ! empty( $calypso_env ) ) {
  168. $url = add_query_arg( array( 'calypso_env' => $calypso_env ), $url );
  169. }
  170. $result = Client::_wp_remote_request( $url, $request );
  171. if ( is_wp_error( $result ) ) {
  172. return $result;
  173. }
  174. $response_code = wp_remote_retrieve_response_code( $result );
  175. $body_json = json_decode( wp_remote_retrieve_body( $result ) );
  176. if ( 200 !== $response_code ) {
  177. if ( isset( $body_json->error ) ) {
  178. return new WP_Error( $body_json->error, $body_json->message );
  179. } else {
  180. return new WP_Error(
  181. 'server_error',
  182. /* translators: %s is an HTTP status code retured from an API request. Ex. – 400 */
  183. sprintf( __( 'Request failed with code %s', 'jetpack' ), $response_code )
  184. );
  185. }
  186. }
  187. if ( isset( $body_json->access_token ) && is_user_logged_in() ) {
  188. // Check if this matches the existing token before replacing.
  189. $existing_token = ( new Tokens() )->get_access_token( get_current_user_id() );
  190. if ( empty( $existing_token ) || $existing_token->secret !== $body_json->access_token ) {
  191. self::authorize_user( get_current_user_id(), $body_json->access_token );
  192. }
  193. }
  194. return $body_json;
  195. }
  196. /**
  197. * Authorizes the passed user.
  198. *
  199. * @param int $user_id User ID.
  200. * @param string $access_token Access token.
  201. */
  202. private static function authorize_user( $user_id, $access_token ) {
  203. // authorize user and enable SSO.
  204. ( new Tokens() )->update_user_token( $user_id, sprintf( '%s.%d', $access_token, $user_id ), true );
  205. /**
  206. * Auto-enable SSO module for new Jetpack Start connections
  207. *
  208. * @since 5.0.0
  209. *
  210. * @param bool $enable_sso Whether to enable the SSO module. Default to true.
  211. */
  212. $other_modules = apply_filters( 'jetpack_start_enable_sso', true )
  213. ? array( 'sso' )
  214. : array();
  215. $active_modules = Jetpack_Options::get_option( 'active_modules' );
  216. if ( $active_modules ) {
  217. Jetpack::delete_active_modules();
  218. Jetpack::activate_default_modules( 999, 1, array_merge( $active_modules, $other_modules ), false );
  219. } else {
  220. Jetpack::activate_default_modules( false, false, $other_modules, false );
  221. }
  222. }
  223. /**
  224. * Verifies the access token being used.
  225. *
  226. * @param string $access_token Access token.
  227. *
  228. * @return array|\Automattic\Jetpack\Connection\WP_Error|bool|WP_Error
  229. */
  230. private static function verify_token( $access_token ) {
  231. $request = array(
  232. 'headers' => array(
  233. 'Authorization' => 'Bearer ' . $access_token,
  234. 'Host' => 'public-api.wordpress.com',
  235. ),
  236. 'timeout' => 10,
  237. 'method' => 'POST',
  238. 'body' => '',
  239. );
  240. $url = sprintf( '%s/rest/v1.3/jpphp/partner-keys/verify', self::get_api_host() );
  241. $result = Client::_wp_remote_request( $url, $request );
  242. if ( is_wp_error( $result ) ) {
  243. return $result;
  244. }
  245. $response_code = wp_remote_retrieve_response_code( $result );
  246. $body_json = json_decode( wp_remote_retrieve_body( $result ) );
  247. if ( 200 !== $response_code ) {
  248. if ( isset( $body_json->error ) ) {
  249. return new WP_Error( $body_json->error, $body_json->message );
  250. } else {
  251. /* translators: %s is HTTP response code (e.g. 500, 401, etc). */
  252. return new WP_Error( 'server_error', sprintf( __( 'Request failed with code %s', 'jetpack' ), $response_code ) );
  253. }
  254. }
  255. return true;
  256. }
  257. /**
  258. * Gets the API host as set via env.
  259. *
  260. * @return string API URL.
  261. */
  262. private static function get_api_host() {
  263. $env_api_host = getenv( 'JETPACK_START_API_HOST', true ); // phpcs:ignore PHPCompatibility.FunctionUse.NewFunctionParameters.getenv_local_onlyFound
  264. return $env_api_host ? 'https://' . $env_api_host : JETPACK__WPCOM_JSON_API_BASE;
  265. }
  266. }