Completed
Push — master ( e54806...b8b1cb )
by Kirk
251:26 queued 244:24
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 $nonce_user_id;
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->nonce_user_id = (int) $user_id;
109
		if ( ! $this->nonce_user_id ) {
110
			return false;
111
		}
112
113
		$token = Jetpack_Data::get_access_token( $this->nonce_user_id );
0 ignored issues
show
Documentation introduced by
$this->nonce_user_id 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
		/*
119
		 * Failures must return `false` (blocking the iframe) prior to the
120
		 * signature verification.
121
		 */
122
123
		add_filter( 'salt', array( $this, 'filter_salt' ), 10, 2 );
124
		$expected_hash = wp_hash( "$expiration|$action|{$this->nonce_user_id}", 'jetpack_frame_nonce' );
125
		remove_filter( 'salt', array( $this, 'filter_salt' ) );
126
127
		if ( ! hash_equals( $hash, $expected_hash ) ) {
128
			return false;
129
		}
130
131
		/*
132
		 * Failures may return `WP_Error` (showing an error in the iframe) after the
133
		 * signature verification passes.
134
		 */
135
136
		if ( time() > $expiration ) {
137
			return new WP_Error( 'nonce_invalid_expired', 'Expired nonce.', array( 'status' => 401 ) );
138
		}
139
140
		if ( get_current_user_id() !== $this->nonce_user_id ) {
141
			return new WP_Error( 'nonce_invalid_user_mismatch', 'User ID mismatch.', array( 'status' => 401 ) );
142
		}
143
144
		return true;
145
	}
146
147
	/**
148
	 * Filters the WordPress salt.
149
	 *
150
	 * @param string $salt   Salt for the given scheme.
151
	 * @param string $scheme Authentication scheme.
152
	 * @return string
153
	 */
154
	public function filter_salt( $salt, $scheme ) {
155
		if ( 'jetpack_frame_nonce' === $scheme ) {
156
			$token = Jetpack_Data::get_access_token( $this->nonce_user_id );
0 ignored issues
show
Documentation introduced by
$this->nonce_user_id 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...
157
158
			if ( $token ) {
159
				$salt = $token->secret;
160
			}
161
		}
162
163
		return $salt;
164
	}
165
}
166
167
Jetpack_WPCOM_Block_Editor::init();
168