/wp-content/plugins/jetpack/vendor/automattic/jetpack-connection/src/class-secrets.php

https://github.com/livinglab/openlab · PHP · 281 lines · 170 code · 30 blank · 81 comment · 12 complexity · a042c255a3756f543657d2b362a2f42f MD5 · raw file

  1. <?php
  2. /**
  3. * The Jetpack Connection Secrets class file.
  4. *
  5. * @package automattic/jetpack-connection
  6. */
  7. namespace Automattic\Jetpack\Connection;
  8. use Jetpack_Options;
  9. use WP_Error;
  10. /**
  11. * The Jetpack Connection Secrets class that is used to manage secrets.
  12. */
  13. class Secrets {
  14. const SECRETS_MISSING = 'secrets_missing';
  15. const SECRETS_EXPIRED = 'secrets_expired';
  16. const LEGACY_SECRETS_OPTION_NAME = 'jetpack_secrets';
  17. /**
  18. * Deletes all connection secrets from the local Jetpack site.
  19. */
  20. public function delete_all() {
  21. Jetpack_Options::delete_raw_option( 'jetpack_secrets' );
  22. }
  23. /**
  24. * Runs the wp_generate_password function with the required parameters. This is the
  25. * default implementation of the secret callable, can be overridden using the
  26. * jetpack_connection_secret_generator filter.
  27. *
  28. * @return String $secret value.
  29. */
  30. private function secret_callable_method() {
  31. $secret = wp_generate_password( 32, false );
  32. // Some sites may hook into the random_password filter and make the password shorter, let's make sure our secret has the required length.
  33. $attempts = 1;
  34. $secret_length = strlen( $secret );
  35. while ( $secret_length < 32 && $attempts < 32 ) {
  36. $attempts++;
  37. $secret .= wp_generate_password( 32, false );
  38. $secret_length = strlen( $secret );
  39. }
  40. return (string) substr( $secret, 0, 32 );
  41. }
  42. /**
  43. * Generates two secret tokens and the end of life timestamp for them.
  44. *
  45. * @param String $action The action name.
  46. * @param Integer|bool $user_id The user identifier. Defaults to `false`.
  47. * @param Integer $exp Expiration time in seconds.
  48. */
  49. public function generate( $action, $user_id = false, $exp = 600 ) {
  50. if ( false === $user_id ) {
  51. $user_id = get_current_user_id();
  52. }
  53. $callable = apply_filters( 'jetpack_connection_secret_generator', array( get_called_class(), 'secret_callable_method' ) );
  54. $secrets = Jetpack_Options::get_raw_option(
  55. self::LEGACY_SECRETS_OPTION_NAME,
  56. array()
  57. );
  58. $secret_name = 'jetpack_' . $action . '_' . $user_id;
  59. if (
  60. isset( $secrets[ $secret_name ] ) &&
  61. $secrets[ $secret_name ]['exp'] > time()
  62. ) {
  63. return $secrets[ $secret_name ];
  64. }
  65. $secret_value = array(
  66. 'secret_1' => call_user_func( $callable ),
  67. 'secret_2' => call_user_func( $callable ),
  68. 'exp' => time() + $exp,
  69. );
  70. $secrets[ $secret_name ] = $secret_value;
  71. $res = Jetpack_Options::update_raw_option( self::LEGACY_SECRETS_OPTION_NAME, $secrets );
  72. return $res ? $secrets[ $secret_name ] : false;
  73. }
  74. /**
  75. * Returns two secret tokens and the end of life timestamp for them.
  76. *
  77. * @param String $action The action name.
  78. * @param Integer $user_id The user identifier.
  79. * @return string|array an array of secrets or an error string.
  80. */
  81. public function get( $action, $user_id ) {
  82. $secret_name = 'jetpack_' . $action . '_' . $user_id;
  83. $secrets = Jetpack_Options::get_raw_option(
  84. self::LEGACY_SECRETS_OPTION_NAME,
  85. array()
  86. );
  87. if ( ! isset( $secrets[ $secret_name ] ) ) {
  88. return self::SECRETS_MISSING;
  89. }
  90. if ( $secrets[ $secret_name ]['exp'] < time() ) {
  91. $this->delete( $action, $user_id );
  92. return self::SECRETS_EXPIRED;
  93. }
  94. return $secrets[ $secret_name ];
  95. }
  96. /**
  97. * Deletes secret tokens in case they, for example, have expired.
  98. *
  99. * @param String $action The action name.
  100. * @param Integer $user_id The user identifier.
  101. */
  102. public function delete( $action, $user_id ) {
  103. $secret_name = 'jetpack_' . $action . '_' . $user_id;
  104. $secrets = Jetpack_Options::get_raw_option(
  105. self::LEGACY_SECRETS_OPTION_NAME,
  106. array()
  107. );
  108. if ( isset( $secrets[ $secret_name ] ) ) {
  109. unset( $secrets[ $secret_name ] );
  110. Jetpack_Options::update_raw_option( self::LEGACY_SECRETS_OPTION_NAME, $secrets );
  111. }
  112. }
  113. /**
  114. * Verify a Previously Generated Secret.
  115. *
  116. * @param string $action The type of secret to verify.
  117. * @param string $secret_1 The secret string to compare to what is stored.
  118. * @param int $user_id The user ID of the owner of the secret.
  119. * @return WP_Error|string WP_Error on failure, secret_2 on success.
  120. */
  121. public function verify( $action, $secret_1, $user_id ) {
  122. $allowed_actions = array( 'register', 'authorize', 'publicize' );
  123. if ( ! in_array( $action, $allowed_actions, true ) ) {
  124. return new WP_Error( 'unknown_verification_action', 'Unknown Verification Action', 400 );
  125. }
  126. $user = get_user_by( 'id', $user_id );
  127. /**
  128. * We've begun verifying the previously generated secret.
  129. *
  130. * @since 1.7.0
  131. * @since-jetpack 7.5.0
  132. *
  133. * @param string $action The type of secret to verify.
  134. * @param \WP_User $user The user object.
  135. */
  136. do_action( 'jetpack_verify_secrets_begin', $action, $user );
  137. $return_error = function ( WP_Error $error ) use ( $action, $user ) {
  138. /**
  139. * Verifying of the previously generated secret has failed.
  140. *
  141. * @since 1.7.0
  142. * @since-jetpack 7.5.0
  143. *
  144. * @param string $action The type of secret to verify.
  145. * @param \WP_User $user The user object.
  146. * @param WP_Error $error The error object.
  147. */
  148. do_action( 'jetpack_verify_secrets_fail', $action, $user, $error );
  149. return $error;
  150. };
  151. $stored_secrets = $this->get( $action, $user_id );
  152. $this->delete( $action, $user_id );
  153. $error = null;
  154. if ( empty( $secret_1 ) ) {
  155. $error = $return_error(
  156. new WP_Error(
  157. 'verify_secret_1_missing',
  158. /* translators: "%s" is the name of a paramter. It can be either "secret_1" or "state". */
  159. sprintf( __( 'The required "%s" parameter is missing.', 'jetpack' ), 'secret_1' ),
  160. 400
  161. )
  162. );
  163. } elseif ( ! is_string( $secret_1 ) ) {
  164. $error = $return_error(
  165. new WP_Error(
  166. 'verify_secret_1_malformed',
  167. /* translators: "%s" is the name of a paramter. It can be either "secret_1" or "state". */
  168. sprintf( __( 'The required "%s" parameter is malformed.', 'jetpack' ), 'secret_1' ),
  169. 400
  170. )
  171. );
  172. } elseif ( empty( $user_id ) ) {
  173. // $user_id is passed around during registration as "state".
  174. $error = $return_error(
  175. new WP_Error(
  176. 'state_missing',
  177. /* translators: "%s" is the name of a paramter. It can be either "secret_1" or "state". */
  178. sprintf( __( 'The required "%s" parameter is missing.', 'jetpack' ), 'state' ),
  179. 400
  180. )
  181. );
  182. } elseif ( ! ctype_digit( (string) $user_id ) ) {
  183. $error = $return_error(
  184. new WP_Error(
  185. 'state_malformed',
  186. /* translators: "%s" is the name of a paramter. It can be either "secret_1" or "state". */
  187. sprintf( __( 'The required "%s" parameter is malformed.', 'jetpack' ), 'state' ),
  188. 400
  189. )
  190. );
  191. } elseif ( self::SECRETS_MISSING === $stored_secrets ) {
  192. $error = $return_error(
  193. new WP_Error(
  194. 'verify_secrets_missing',
  195. __( 'Verification secrets not found', 'jetpack' ),
  196. 400
  197. )
  198. );
  199. } elseif ( self::SECRETS_EXPIRED === $stored_secrets ) {
  200. $error = $return_error(
  201. new WP_Error(
  202. 'verify_secrets_expired',
  203. __( 'Verification took too long', 'jetpack' ),
  204. 400
  205. )
  206. );
  207. } elseif ( ! $stored_secrets ) {
  208. $error = $return_error(
  209. new WP_Error(
  210. 'verify_secrets_empty',
  211. __( 'Verification secrets are empty', 'jetpack' ),
  212. 400
  213. )
  214. );
  215. } elseif ( is_wp_error( $stored_secrets ) ) {
  216. $stored_secrets->add_data( 400 );
  217. $error = $return_error( $stored_secrets );
  218. } elseif ( empty( $stored_secrets['secret_1'] ) || empty( $stored_secrets['secret_2'] ) || empty( $stored_secrets['exp'] ) ) {
  219. $error = $return_error(
  220. new WP_Error(
  221. 'verify_secrets_incomplete',
  222. __( 'Verification secrets are incomplete', 'jetpack' ),
  223. 400
  224. )
  225. );
  226. } elseif ( ! hash_equals( $secret_1, $stored_secrets['secret_1'] ) ) {
  227. $error = $return_error(
  228. new WP_Error(
  229. 'verify_secrets_mismatch',
  230. __( 'Secret mismatch', 'jetpack' ),
  231. 400
  232. )
  233. );
  234. }
  235. // Something went wrong during the checks, returning the error.
  236. if ( ! empty( $error ) ) {
  237. return $error;
  238. }
  239. /**
  240. * We've succeeded at verifying the previously generated secret.
  241. *
  242. * @since 1.7.0
  243. * @since-jetpack 7.5.0
  244. *
  245. * @param string $action The type of secret to verify.
  246. * @param \WP_User $user The user object.
  247. */
  248. do_action( 'jetpack_verify_secrets_success', $action, $user );
  249. return $stored_secrets['secret_2'];
  250. }
  251. }