PluginProbe
Jetpack – WP Security, Backup, Speed, & Growth / 9.8.2
Jetpack – WP Security, Backup, Speed, & Growth v9.8.2
16.2-beta 12.0.3 12.1.3 12.2.3 12.3.2 12.4.2 12.5.2 12.6.4 12.7.3 12.8.3 12.9.5 13.0.2 13.1.5 13.2.4 13.3.3 13.4.5 13.5.2 13.6.2 13.7.2 13.8.3 13.9.2 14.0.1 14.1.1 14.2.2 14.3.1 All 501 releases
jetpack / modules / sharedaddy / recaptcha.php
recaptcha.php
224 lines 6.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 /**
4 * Class that handles reCAPTCHA.
5 */
6 class Jetpack_ReCaptcha {
7
8 /**
9 * URL to which requests are POSTed.
10 *
11 * @const string
12 */
13 const VERIFY_URL = 'https://www.google.com/recaptcha/api/siteverify';
14
15 /**
16 * Site key to use in HTML code.
17 *
18 * @var string
19 */
20 private $site_key;
21
22 /**
23 * Shared secret for the site.
24 *
25 * @var string
26 */
27 private $secret_key;
28
29 /**
30 * Config for reCAPTCHA instance.
31 *
32 * @var array
33 */
34 private $config;
35
36 /**
37 * Error codes returned from reCAPTCHA API.
38 *
39 * @see https://developers.google.com/recaptcha/docs/verify
40 *
41 * @var array
42 */
43 private $error_codes;
44
45 /**
46 * Create a configured instance to use the reCAPTCHA service.
47 *
48 * @param string $site_key Site key to use in HTML code.
49 * @param string $secret_key Shared secret between site and reCAPTCHA server.
50 * @param array $config Config array to optionally configure reCAPTCHA instance.
51 */
52 public function __construct( $site_key, $secret_key, $config = array() ) {
53 $this->site_key = $site_key;
54 $this->secret_key = $secret_key;
55 $this->config = wp_parse_args( $config, $this->get_default_config() );
56
57 $this->error_codes = array(
58 'missing-input-secret' => __( 'The secret parameter is missing', 'jetpack' ),
59 'invalid-input-secret' => __( 'The secret parameter is invalid or malformed', 'jetpack' ),
60 'missing-input-response' => __( 'The response parameter is missing', 'jetpack' ),
61 'invalid-input-response' => __( 'The response parameter is invalid or malformed', 'jetpack' ),
62 'invalid-json' => __( 'Invalid JSON', 'jetpack' ),
63 'unexpected-response' => __( 'Unexpected response', 'jetpack' ),
64 'unexpected-hostname' => __( 'Unexpected hostname', 'jetpack' ),
65 );
66 }
67
68 /**
69 * Get default config for this reCAPTCHA instance.
70 *
71 * @return array Default config
72 */
73 public function get_default_config() {
74 return array(
75 'language' => get_locale(),
76 'script_async' => false,
77 'script_defer' => true,
78 'script_lazy' => false,
79 'tag_class' => 'g-recaptcha',
80 'tag_attributes' => array(
81 'theme' => 'light',
82 'type' => 'image',
83 'tabindex' => 0,
84 ),
85 );
86 }
87
88 /**
89 * Calls the reCAPTCHA siteverify API to verify whether the user passes
90 * CAPTCHA test.
91 *
92 * @param string $response The value of 'g-recaptcha-response' in the submitted
93 * form.
94 * @param string $remote_ip The end user's IP address.
95 *
96 * @return bool|WP_Error Returns true if verified. Otherwise WP_Error is returned.
97 */
98 public function verify( $response, $remote_ip ) {
99 // No need make a request if response is empty.
100 if ( empty( $response ) ) {
101 return new WP_Error( 'missing-input-response', $this->error_codes['missing-input-response'], 400 );
102 }
103
104 $resp = wp_remote_post( self::VERIFY_URL, $this->get_verify_request_params( $response, $remote_ip ) );
105 if ( is_wp_error( $resp ) ) {
106 return $resp;
107 }
108
109 $resp_decoded = json_decode( wp_remote_retrieve_body( $resp ), true );
110 if ( ! $resp_decoded ) {
111 return new WP_Error( 'invalid-json', $this->error_codes['invalid-json'], 400 );
112 }
113
114 // Default error code and message.
115 $error_code = 'unexpected-response';
116 $error_message = $this->error_codes['unexpected-response'];
117
118 // Use the first error code if exists.
119 if ( isset( $resp_decoded['error-codes'] ) && is_array( $resp_decoded['error-codes'] ) ) {
120 if ( isset( $resp_decoded['error-codes'][0] ) && isset( $this->error_codes[ $resp_decoded['error-codes'][0] ] ) ) {
121 $error_message = $this->error_codes[ $resp_decoded['error-codes'][0] ];
122 $error_code = $resp_decoded['error-codes'][0];
123 }
124 }
125
126 if ( ! isset( $resp_decoded['success'] ) ) {
127 return new WP_Error( $error_code, $error_message );
128 }
129
130 if ( true !== $resp_decoded['success'] ) {
131 return new WP_Error( $error_code, $error_message );
132 }
133 // Validate the hostname matches expected source
134 if ( isset( $resp_decoded['hostname'] ) ) {
135 $url = wp_parse_url( get_home_url() );
136
137 /**
138 * Allow other valid hostnames.
139 *
140 * This can be useful in cases where the token hostname is expected to be
141 * different from the get_home_url (ex. AMP recaptcha token contains a different hostname)
142 *
143 * @module sharedaddy
144 *
145 * @since 9.1.0
146 *
147 * @param array [ $url['host'] ] List of the valid hostnames to check against.
148 */
149 $valid_hostnames = apply_filters( 'jetpack_recaptcha_valid_hostnames', array( $url['host'] ) );
150
151 if ( ! in_array( $resp_decoded['hostname'], $valid_hostnames, true ) ) {
152 return new WP_Error( 'unexpected-host', $this->error_codes['unexpected-hostname'] );
153 }
154 }
155
156 return true;
157 }
158
159 /**
160 * Get siteverify request parameters.
161 *
162 * @param string $response The value of 'g-recaptcha-response' in the submitted
163 * form.
164 * @param string $remote_ip The end user's IP address.
165 *
166 * @return array
167 */
168 public function get_verify_request_params( $response, $remote_ip ) {
169 return array(
170 'body' => array(
171 'secret' => $this->secret_key,
172 'response' => $response,
173 'remoteip' => $remote_ip,
174 ),
175 'sslverify' => true,
176 );
177 }
178
179 /**
180 * Get reCAPTCHA HTML to render.
181 *
182 * @return string
183 */
184 public function get_recaptcha_html() {
185 $url = sprintf(
186 'https://www.google.com/recaptcha/api.js?hl=%s',
187 rawurlencode( $this->config['language'] )
188 );
189
190 $html = sprintf(
191 '
192 <div
193 class="%s"
194 data-sitekey="%s"
195 data-theme="%s"
196 data-type="%s"
197 data-tabindex="%s"
198 data-lazy="%s"
199 data-url="%s"></div>
200 ',
201 esc_attr( $this->config['tag_class'] ),
202 esc_attr( $this->site_key ),
203 esc_attr( $this->config['tag_attributes']['theme'] ),
204 esc_attr( $this->config['tag_attributes']['type'] ),
205 esc_attr( $this->config['tag_attributes']['tabindex'] ),
206 $this->config['script_lazy'] ? 'true' : 'false',
207 esc_attr( $url )
208 );
209
210 if ( ! $this->config['script_lazy'] ) {
211 $html = $html . sprintf(
212 // phpcs:ignore WordPress.WP.EnqueuedResources.NonEnqueuedScript
213 '<script src="%s"%s%s></script>
214 ',
215 $url,
216 $this->config['script_async'] && ! $this->config['script_defer'] ? ' async' : '',
217 $this->config['script_defer'] ? ' defer' : ''
218 );
219 }
220
221 return $html;
222 }
223 }
224