Completed
Push — try/block-editor-iframe ( 901ea1...9c762e )
by
unknown
06:41
created

Jetpack_WPCOM_Block_Editor::init()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
nc 2
nop 0
dl 0
loc 9
rs 9.9666
c 0
b 0
f 0
1
<?php
2
/**
3
 * WordPress.com Block Editor
4
 * Allow new block editor posts to be composed on WordPress.com.
5
 * This is auto-loaded as of Jetpack v7.4 for sites connected to WordPress.com only.
6
 *
7
 * @package Jetpack
8
 */
9
10
/**
11
 * WordPress.com Block editor for Jetpack
12
 */
13
class Jetpack_WPCOM_Block_Editor {
14
	/**
15
	 * ID of the user who signed the nonce.
16
	 *
17
	 * @var int
18
	 */
19
	private $user_id_nonce;
20
21
	/**
22
	 * Singleton
23
	 */
24
	public static function init() {
25
		static $instance = false;
26
27
		if ( ! $instance ) {
28
			$instance = new Jetpack_WPCOM_Block_Editor();
29
		}
30
31
		return $instance;
32
	}
33
34
	/**
35
	 * Jetpack_WPCOM_Block_Editor constructor.
36
	 */
37
	private function __construct() {
38
		add_action( 'admin_init', array( $this, 'disable_send_frame_options_header' ), 9 );
39
		add_filter( 'admin_body_class', array( $this, 'add_iframed_body_class' ) );
40
	}
41
42
	/**
43
	 * Prevents frame options header from firing if this is a whitelisted iframe request.
44
	 */
45
	public function disable_send_frame_options_header() {
46
		if ( $this->framing_allowed() ) {
47
			remove_action( 'admin_init', 'send_frame_options_header' );
48
		}
49
	}
50
51
	/**
52
	 * Adds custom admin body class if this is a whitelisted iframe request.
53
	 *
54
	 * @param string $classes Admin body classes.
55
	 * @return string
56
	 */
57
	public function add_iframed_body_class( $classes ) {
58
		if ( $this->framing_allowed() ) {
59
			$classes .= ' is-iframed ';
60
		}
61
62
		return $classes;
63
	}
64
65
	/**
66
	 * Checks whether this is a whitelisted iframe request.
67
	 *
68
	 * @return bool
69
	 */
70
	public function framing_allowed() {
71
		if ( empty( $_GET['frame-nonce'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification
72
			return false;
73
		}
74
75
		$verified = $this->verify_frame_nonce(
76
			$_GET['frame-nonce'],  // phpcs:ignore WordPress.Security.NonceVerification
77
			'frame-' . Jetpack_Options::get_option( 'id' )
78
		);
79
80
		if ( is_wp_error( $verified ) ) {
81
			wp_die( $verified );
82
		}
83
84
		if ( $verified && ! defined( 'IFRAME_REQUEST' ) ) {
85
			define( 'IFRAME_REQUEST', true );
86
		}
87
88
		return (bool) $verified;
89
	}
90
91
	/**
92
	 * Verify that correct nonce was used with time limit.
93
	 *
94
	 * The user is given an amount of time to use the token, so therefore, since the
95
	 * UID and $action remain the same, the independent variable is the time.
96
	 *
97
	 * @param string $nonce  Nonce that was used in the form to verify.
98
	 * @param string $action Should give context to what is taking place and be the same when nonce was created.
99
	 * @return boolean|WP_Error Whether the nonce is valid.
100
	 */
101
	public function verify_frame_nonce( $nonce, $action ) {
102
		if ( empty( $nonce ) ) {
103
			return false;
104
		}
105
106
		list( $expiration, $user_id, $hash ) = explode( ':', $nonce, 3 );
107
108
		$this->user_id_nonce = (int) $user_id;
109
		if ( ! $this->user_id_nonce ) {
110
			return false;
111
		}
112
113
		$token = Jetpack_Data::get_access_token( $this->user_id_nonce );
0 ignored issues
show
Documentation introduced by
$this->user_id_nonce is of type integer, but the function expects a boolean.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
114
		if ( ! $token ) {
115
			return false;
116
		}
117
118
		add_filter( 'salt', array( $this, 'filter_salt' ), 10, 2 );
119
		$expected_hash = wp_hash( "$expiration|$action|{$token->external_user_id}", 'jetpack_frame_nonce' );
120
		remove_filter( 'salt', array( $this, 'filter_salt' ) );
121
122
		if ( ! hash_equals( $hash, $expected_hash ) ) {
123
			return false;
124
		}
125
126
		if ( time() > $expiration ) {
127
			return new WP_Error( 'nonce_invalid_expired', 'Expired nonce.' );
128
		}
129
130
		$current_user_id = get_current_user_id();
131
		if ( ! $current_user_id || $current_user_id !== $this->user_id_nonce ) {
132
			return new WP_Error( 'nonce_invalid_user_mismatch', 'User ID mismatch.' );
133
		}
134
135
		return true;
136
	}
137
138
	/**
139
	 * Filters the WordPress salt.
140
	 *
141
	 * @param string $salt   Salt for the given scheme.
142
	 * @param string $scheme Authentication scheme.
143
	 * @return string
144
	 */
145
	public function filter_salt( $salt, $scheme ) {
146
		if ( 'jetpack_frame_nonce' === $scheme ) {
147
			$token = Jetpack_Data::get_access_token( $this->user_id_nonce );
0 ignored issues
show
Documentation introduced by
$this->user_id_nonce is of type integer, but the function expects a boolean.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
148
149
			if ( $token ) {
150
				$salt = $token->secret;
151
			}
152
		}
153
154
		return $salt;
155
	}
156
}
157
158
Jetpack_WPCOM_Block_Editor::init();
159