|
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' => true, |
|
77
|
|
|
'tag_class' => 'g-recaptcha', |
|
78
|
|
|
'tag_attributes' => array( |
|
79
|
|
|
'theme' => 'light', |
|
80
|
|
|
'type' => 'image', |
|
81
|
|
|
'tabindex' => 0, |
|
82
|
|
|
), |
|
83
|
|
|
); |
|
84
|
|
|
} |
|
85
|
|
|
|
|
86
|
|
|
/** |
|
87
|
|
|
* Calls the reCAPTCHA siteverify API to verify whether the user passes |
|
88
|
|
|
* CAPTCHA test. |
|
89
|
|
|
* |
|
90
|
|
|
* @param string $response The value of 'g-recaptcha-response' in the submitted |
|
91
|
|
|
* form. |
|
92
|
|
|
* @param string $remote_ip The end user's IP address. |
|
93
|
|
|
* |
|
94
|
|
|
* @return bool|WP_Error Returns true if verified. Otherwise WP_Error is returned. |
|
95
|
|
|
*/ |
|
96
|
|
|
public function verify( $response, $remote_ip ) { |
|
97
|
|
|
// No need make a request if response is empty. |
|
98
|
|
|
if ( empty( $response ) ) { |
|
99
|
|
|
return new WP_Error( 'missing-input-response', $this->error_codes['missing-input-response'], 400 ); |
|
|
|
|
|
|
100
|
|
|
} |
|
101
|
|
|
|
|
102
|
|
|
$resp = wp_remote_post( self::VERIFY_URL, $this->get_verify_request_params( $response, $remote_ip ) ); |
|
103
|
|
|
if ( is_wp_error( $resp ) ) { |
|
104
|
|
|
return $resp; |
|
105
|
|
|
} |
|
106
|
|
|
|
|
107
|
|
|
$resp_decoded = json_decode( wp_remote_retrieve_body( $resp ), true ); |
|
108
|
|
|
if ( ! $resp_decoded ) { |
|
109
|
|
|
return new WP_Error( 'invalid-json', $this->error_codes['invalid-json'], 400 ); |
|
|
|
|
|
|
110
|
|
|
} |
|
111
|
|
|
|
|
112
|
|
|
// Default error code and message. |
|
113
|
|
|
$error_code = 'unexpected-response'; |
|
114
|
|
|
$error_message = $this->error_codes['unexpected-response']; |
|
115
|
|
|
|
|
116
|
|
|
// Use the first error code if exists. |
|
117
|
|
|
if ( isset( $resp_decoded['error-codes'] ) && is_array( $resp_decoded['error-codes'] ) ) { |
|
118
|
|
|
if ( isset( $resp_decoded['error-codes'][0] ) && isset( $this->error_codes[ $resp_decoded['error-codes'][0] ] ) ) { |
|
119
|
|
|
$error_message = $this->error_codes[ $resp_decoded['error-codes'][0] ]; |
|
120
|
|
|
$error_code = $resp_decoded['error-codes'][0]; |
|
121
|
|
|
} |
|
122
|
|
|
} |
|
123
|
|
|
|
|
124
|
|
|
if ( ! isset( $resp_decoded['success'] ) ) { |
|
125
|
|
|
return new WP_Error( $error_code, $error_message ); |
|
|
|
|
|
|
126
|
|
|
} |
|
127
|
|
|
|
|
128
|
|
|
if ( true !== $resp_decoded['success'] ) { |
|
129
|
|
|
return new WP_Error( $error_code, $error_message ); |
|
|
|
|
|
|
130
|
|
|
} |
|
131
|
|
|
|
|
132
|
|
|
// Validate the hostname matches expected source |
|
133
|
|
|
if ( isset( $resp_decoded['hostname'] ) ) { |
|
134
|
|
|
$url = wp_parse_url( get_home_url() ); |
|
135
|
|
|
if ( $url['host'] !== $resp_decoded['hostname'] ) { |
|
136
|
|
|
return new WP_Error( 'unexpected-host', $this->error_codes['unexpected-hostname'] ); |
|
|
|
|
|
|
137
|
|
|
} |
|
138
|
|
|
} |
|
139
|
|
|
|
|
140
|
|
|
return true; |
|
141
|
|
|
} |
|
142
|
|
|
|
|
143
|
|
|
/** |
|
144
|
|
|
* Get siteverify request parameters. |
|
145
|
|
|
* |
|
146
|
|
|
* @param string $response The value of 'g-recaptcha-response' in the submitted |
|
147
|
|
|
* form. |
|
148
|
|
|
* @param string $remote_ip The end user's IP address. |
|
149
|
|
|
* |
|
150
|
|
|
* @return array |
|
151
|
|
|
*/ |
|
152
|
|
|
public function get_verify_request_params( $response, $remote_ip ) { |
|
153
|
|
|
return array( |
|
154
|
|
|
'body' => array( |
|
155
|
|
|
'secret' => $this->secret_key, |
|
156
|
|
|
'response' => $response, |
|
157
|
|
|
'remoteip' => $remote_ip, |
|
158
|
|
|
), |
|
159
|
|
|
'sslverify' => true, |
|
160
|
|
|
); |
|
161
|
|
|
} |
|
162
|
|
|
|
|
163
|
|
|
/** |
|
164
|
|
|
* Get reCAPTCHA HTML to render. |
|
165
|
|
|
* |
|
166
|
|
|
* @return string |
|
167
|
|
|
*/ |
|
168
|
|
|
public function get_recaptcha_html() { |
|
169
|
|
|
return sprintf( |
|
170
|
|
|
' |
|
171
|
|
|
<div |
|
172
|
|
|
class="%s" |
|
173
|
|
|
data-sitekey="%s" |
|
174
|
|
|
data-theme="%s" |
|
175
|
|
|
data-type="%s" |
|
176
|
|
|
data-tabindex="%s"></div> |
|
177
|
|
|
<script type="text/javascript" src="https://www.google.com/recaptcha/api.js?hl=%s"%s></script> |
|
178
|
|
|
', |
|
179
|
|
|
esc_attr( $this->config['tag_class'] ), |
|
180
|
|
|
esc_attr( $this->site_key ), |
|
181
|
|
|
esc_attr( $this->config['tag_attributes']['theme'] ), |
|
182
|
|
|
esc_attr( $this->config['tag_attributes']['type'] ), |
|
183
|
|
|
esc_attr( $this->config['tag_attributes']['tabindex'] ), |
|
184
|
|
|
rawurlencode( $this->config['language'] ), |
|
185
|
|
|
$this->config['script_async'] ? ' async' : '' |
|
186
|
|
|
); |
|
187
|
|
|
} |
|
188
|
|
|
} |
|
189
|
|
|
|
This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.
If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.
In this case you can add the
@ignorePhpDoc annotation to the duplicate definition and it will be ignored.