1 | <?php |
||||
2 | |||||
3 | /** |
||||
4 | * Copyright (c) 2017 - present |
||||
5 | * LaravelGoogleRecaptcha - ReCaptchaBuilder.php |
||||
6 | * author: Roberto Belotti - [email protected] |
||||
7 | * web : robertobelotti.com, github.com/biscolab |
||||
8 | * Initial version created on: 12/9/2018 |
||||
9 | * MIT license: https://github.com/biscolab/laravel-recaptcha/blob/master/LICENSE |
||||
10 | */ |
||||
11 | |||||
12 | namespace Biscolab\ReCaptcha; |
||||
13 | |||||
14 | use Illuminate\Support\Arr; |
||||
15 | |||||
16 | /** |
||||
17 | * Class ReCaptchaBuilder |
||||
18 | * @package Biscolab\ReCaptcha |
||||
19 | */ |
||||
20 | class ReCaptchaBuilder |
||||
21 | { |
||||
22 | |||||
23 | /** |
||||
24 | * @var string |
||||
25 | */ |
||||
26 | const DEFAULT_API_VERSION = 'v2'; |
||||
27 | |||||
28 | /** |
||||
29 | * @var int |
||||
30 | */ |
||||
31 | const DEFAULT_CURL_TIMEOUT = 10; |
||||
32 | |||||
33 | /** |
||||
34 | * @var string |
||||
35 | */ |
||||
36 | const DEFAULT_ONLOAD_JS_FUNCTION = 'biscolabOnloadCallback'; |
||||
37 | |||||
38 | /** |
||||
39 | * @var string |
||||
40 | */ |
||||
41 | const DEFAULT_RECAPTCHA_RULE_NAME = 'recaptcha'; |
||||
42 | |||||
43 | /** |
||||
44 | * @var string |
||||
45 | */ |
||||
46 | const DEFAULT_RECAPTCHA_FIELD_NAME = 'g-recaptcha-response'; |
||||
47 | |||||
48 | /** |
||||
49 | * @var string |
||||
50 | */ |
||||
51 | const DEFAULT_RECAPTCHA_API_DOMAIN = 'www.google.com'; |
||||
52 | |||||
53 | /** |
||||
54 | * The Site key |
||||
55 | * please visit https://developers.google.com/recaptcha/docs/start |
||||
56 | * @var string |
||||
57 | */ |
||||
58 | protected $api_site_key; |
||||
59 | |||||
60 | /** |
||||
61 | * The Secret key |
||||
62 | * please visit https://developers.google.com/recaptcha/docs/start |
||||
63 | * @var string |
||||
64 | */ |
||||
65 | protected $api_secret_key; |
||||
66 | |||||
67 | /** |
||||
68 | * The chosen ReCAPTCHA version |
||||
69 | * please visit https://developers.google.com/recaptcha/docs/start |
||||
70 | * @var string |
||||
71 | */ |
||||
72 | protected $version; |
||||
73 | |||||
74 | /** |
||||
75 | * Whether is true the ReCAPTCHA is inactive |
||||
76 | * @var boolean |
||||
77 | */ |
||||
78 | protected $skip_by_ip = false; |
||||
79 | |||||
80 | /** |
||||
81 | * The API domain (default: retrieved from config file) |
||||
82 | * @var string |
||||
83 | */ |
||||
84 | protected $api_domain = ''; |
||||
85 | |||||
86 | /** |
||||
87 | * The API request URI |
||||
88 | * @var string |
||||
89 | */ |
||||
90 | protected $api_url = ''; |
||||
91 | |||||
92 | /** |
||||
93 | * The URI of the API Javascript file to embed in you pages |
||||
94 | * @var string |
||||
95 | */ |
||||
96 | protected $api_js_url = ''; |
||||
97 | |||||
98 | /** |
||||
99 | * ReCaptchaBuilder constructor. |
||||
100 | * |
||||
101 | * @param string $api_site_key |
||||
102 | * @param string $api_secret_key |
||||
103 | * @param null|string $version |
||||
104 | */ |
||||
105 | 48 | public function __construct( |
|||
106 | string $api_site_key, |
||||
107 | string $api_secret_key, |
||||
108 | ?string $version = self::DEFAULT_API_VERSION |
||||
109 | ) { |
||||
110 | |||||
111 | 48 | $this->setApiSiteKey($api_site_key); |
|||
112 | 48 | $this->setApiSecretKey($api_secret_key); |
|||
113 | 48 | $this->setVersion($version); |
|||
0 ignored issues
–
show
Bug
introduced
by
Loading history...
|
|||||
114 | 48 | $this->setSkipByIp($this->skipByIp()); |
|||
115 | 48 | $this->setApiDomain(); |
|||
116 | 48 | $this->setApiUrls(); |
|||
117 | 48 | } |
|||
118 | |||||
119 | /** |
||||
120 | * @param string $api_site_key |
||||
121 | * |
||||
122 | * @return ReCaptchaBuilder |
||||
123 | */ |
||||
124 | 48 | public function setApiSiteKey(string $api_site_key): ReCaptchaBuilder |
|||
125 | { |
||||
126 | |||||
127 | 48 | $this->api_site_key = $api_site_key; |
|||
128 | |||||
129 | 48 | return $this; |
|||
130 | } |
||||
131 | |||||
132 | /** |
||||
133 | * @param string $api_secret_key |
||||
134 | * |
||||
135 | * @return ReCaptchaBuilder |
||||
136 | */ |
||||
137 | 48 | public function setApiSecretKey(string $api_secret_key): ReCaptchaBuilder |
|||
138 | { |
||||
139 | |||||
140 | 48 | $this->api_secret_key = $api_secret_key; |
|||
141 | |||||
142 | 48 | return $this; |
|||
143 | } |
||||
144 | |||||
145 | /** |
||||
146 | * @return int |
||||
147 | */ |
||||
148 | 4 | public function getCurlTimeout(): int |
|||
149 | { |
||||
150 | |||||
151 | 4 | return config('recaptcha.curl_timeout', self::DEFAULT_CURL_TIMEOUT); |
|||
152 | } |
||||
153 | |||||
154 | /** |
||||
155 | * @param string $version |
||||
156 | * |
||||
157 | * @return ReCaptchaBuilder |
||||
158 | */ |
||||
159 | 48 | public function setVersion(string $version): ReCaptchaBuilder |
|||
160 | { |
||||
161 | |||||
162 | 48 | $this->version = $version; |
|||
163 | |||||
164 | 48 | return $this; |
|||
165 | } |
||||
166 | |||||
167 | /** |
||||
168 | * @return string |
||||
169 | */ |
||||
170 | 1 | public function getVersion(): string |
|||
171 | { |
||||
172 | |||||
173 | 1 | return $this->version; |
|||
174 | } |
||||
175 | |||||
176 | /** |
||||
177 | * @param bool $skip_by_ip |
||||
178 | * |
||||
179 | * @return ReCaptchaBuilder |
||||
180 | */ |
||||
181 | 48 | public function setSkipByIp(bool $skip_by_ip): ReCaptchaBuilder |
|||
182 | { |
||||
183 | |||||
184 | 48 | $this->skip_by_ip = $skip_by_ip; |
|||
185 | |||||
186 | 48 | return $this; |
|||
187 | } |
||||
188 | |||||
189 | /** |
||||
190 | * @param null|string $api_domain |
||||
191 | * |
||||
192 | * @return ReCaptchaBuilder |
||||
193 | */ |
||||
194 | 48 | public function setApiDomain(?string $api_domain = null): ReCaptchaBuilder |
|||
195 | { |
||||
196 | |||||
197 | 48 | $this->api_domain = $api_domain ?? config('recaptcha.api_domain', self::DEFAULT_RECAPTCHA_API_DOMAIN); |
|||
198 | |||||
199 | 48 | return $this; |
|||
200 | } |
||||
201 | |||||
202 | /** |
||||
203 | * @return string |
||||
204 | */ |
||||
205 | 2 | public function getApiDomain(): string |
|||
206 | { |
||||
207 | |||||
208 | 2 | return $this->api_domain; |
|||
209 | } |
||||
210 | |||||
211 | /** |
||||
212 | * @return ReCaptchaBuilder |
||||
213 | */ |
||||
214 | 48 | public function setApiUrls(): ReCaptchaBuilder |
|||
215 | { |
||||
216 | |||||
217 | 48 | $this->api_url = 'https://' . $this->api_domain . '/recaptcha/api/siteverify'; |
|||
218 | 48 | $this->api_js_url = 'https://' . $this->api_domain . '/recaptcha/api.js'; |
|||
219 | |||||
220 | 48 | return $this; |
|||
221 | } |
||||
222 | |||||
223 | /** |
||||
224 | * @return array|mixed |
||||
225 | */ |
||||
226 | 48 | public function getIpWhitelist() |
|||
227 | { |
||||
228 | |||||
229 | 48 | $whitelist = config('recaptcha.skip_ip', []); |
|||
230 | |||||
231 | 48 | if (!is_array($whitelist)) { |
|||
232 | 4 | $whitelist = explode(',', $whitelist); |
|||
233 | } |
||||
234 | |||||
235 | $whitelist = array_map(function ($item) { |
||||
236 | |||||
237 | 4 | return trim($item); |
|||
238 | 48 | }, $whitelist); |
|||
239 | |||||
240 | 48 | return $whitelist; |
|||
241 | } |
||||
242 | |||||
243 | /** |
||||
244 | * Checks whether the user IP address is among IPs "to be skipped" |
||||
245 | * |
||||
246 | * @return boolean |
||||
247 | */ |
||||
248 | 48 | public function skipByIp(): bool |
|||
249 | { |
||||
250 | |||||
251 | 48 | return (in_array(request()->ip(), $this->getIpWhitelist())); |
|||
252 | } |
||||
253 | |||||
254 | /** |
||||
255 | * Write script HTML tag in you HTML code |
||||
256 | * Insert before </head> tag |
||||
257 | * |
||||
258 | * @param array|null $configuration |
||||
259 | * |
||||
260 | * @return string |
||||
261 | * @throws \Exception |
||||
262 | */ |
||||
263 | 6 | public function htmlScriptTagJsApi(?array $configuration = []): string |
|||
264 | { |
||||
265 | |||||
266 | 6 | $query = []; |
|||
267 | 6 | $html = ''; |
|||
268 | |||||
269 | // Language: "hl" parameter |
||||
270 | // resources $configuration parameter overrides default language |
||||
271 | 6 | $language = Arr::get($configuration, 'lang'); |
|||
272 | 6 | if (!$language) { |
|||
273 | 5 | $language = config('recaptcha.default_language', null); |
|||
274 | } |
||||
275 | 6 | if ($language) { |
|||
276 | 2 | Arr::set($query, 'hl', $language); |
|||
277 | } |
||||
278 | |||||
279 | // Onload JS callback function: "onload" parameter |
||||
280 | // "render" parameter set to "explicit" |
||||
281 | 6 | if (config('recaptcha.explicit', null) && $this->version === 'v2') { |
|||
282 | 2 | Arr::set($query, 'render', 'explicit'); |
|||
283 | 2 | Arr::set($query, 'onload', self::DEFAULT_ONLOAD_JS_FUNCTION); |
|||
284 | |||||
285 | /** @scrutinizer ignore-call */ |
||||
286 | 2 | $html = $this->getOnLoadCallback(); |
|||
287 | } |
||||
288 | |||||
289 | // Create query string |
||||
290 | 5 | $query = ($query) ? '?' . http_build_query($query) : ""; |
|||
291 | 5 | $html .= "<script src=\"" . $this->api_js_url . $query . "\" async defer></script>"; |
|||
292 | |||||
293 | 5 | return $html; |
|||
294 | } |
||||
295 | |||||
296 | /** |
||||
297 | * Call out to reCAPTCHA and process the response |
||||
298 | * |
||||
299 | * @param string $response |
||||
300 | * |
||||
301 | * @return boolean|array |
||||
302 | */ |
||||
303 | 3 | public function validate($response) |
|||
304 | { |
||||
305 | |||||
306 | 3 | if ($this->skip_by_ip) { |
|||
307 | 2 | if ($this->returnArray()) { |
|||
308 | // Add 'skip_by_ip' field to response |
||||
309 | return [ |
||||
310 | 1 | 'skip_by_ip' => true, |
|||
311 | 'score' => 0.9, |
||||
312 | 'success' => true |
||||
313 | ]; |
||||
314 | } |
||||
315 | |||||
316 | 1 | return true; |
|||
317 | } |
||||
318 | |||||
319 | 1 | $params = http_build_query([ |
|||
320 | 1 | 'secret' => $this->api_secret_key, |
|||
321 | 1 | 'remoteip' => request()->getClientIp(), |
|||
322 | 1 | 'response' => $response, |
|||
323 | ]); |
||||
324 | |||||
325 | 1 | $url = $this->api_url . '?' . $params; |
|||
326 | |||||
327 | 1 | if (function_exists('curl_version')) { |
|||
328 | |||||
329 | 1 | $curl = curl_init($url); |
|||
330 | 1 | curl_setopt($curl, CURLOPT_HEADER, false); |
|||
0 ignored issues
–
show
It seems like
$curl can also be of type false ; however, parameter $ch of curl_setopt() does only seem to accept resource , maybe add an additional type check?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
Loading history...
|
|||||
331 | 1 | curl_setopt($curl, CURLOPT_RETURNTRANSFER, true); |
|||
332 | 1 | curl_setopt($curl, CURLOPT_TIMEOUT, $this->getCurlTimeout()); |
|||
333 | 1 | curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false); |
|||
334 | 1 | $curl_response = curl_exec($curl); |
|||
0 ignored issues
–
show
It seems like
$curl can also be of type false ; however, parameter $ch of curl_exec() does only seem to accept resource , maybe add an additional type check?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
Loading history...
|
|||||
335 | } else { |
||||
336 | $curl_response = file_get_contents($url); |
||||
337 | } |
||||
338 | |||||
339 | 1 | if (is_null($curl_response) || empty($curl_response)) { |
|||
340 | if ($this->returnArray()) { |
||||
341 | // Add 'error' field to response |
||||
342 | return [ |
||||
343 | 'error' => 'cURL response empty', |
||||
344 | 'score' => 0.1, |
||||
345 | 'success' => false |
||||
346 | ]; |
||||
347 | } |
||||
348 | |||||
349 | return false; |
||||
350 | } |
||||
351 | 1 | $response = json_decode(trim($curl_response), true); |
|||
352 | |||||
353 | 1 | if ($this->returnArray()) { |
|||
354 | 1 | return $response; |
|||
355 | } |
||||
356 | |||||
357 | return $response['success']; |
||||
358 | } |
||||
359 | |||||
360 | /** |
||||
361 | * @return string |
||||
362 | */ |
||||
363 | 1 | public function getApiSiteKey(): string |
|||
364 | { |
||||
365 | |||||
366 | 1 | return $this->api_site_key; |
|||
367 | } |
||||
368 | |||||
369 | /** |
||||
370 | * @return string |
||||
371 | */ |
||||
372 | 1 | public function getApiSecretKey(): string |
|||
373 | { |
||||
374 | |||||
375 | 1 | return $this->api_secret_key; |
|||
376 | } |
||||
377 | |||||
378 | /** |
||||
379 | * @return bool |
||||
380 | */ |
||||
381 | 3 | protected function returnArray(): bool |
|||
382 | { |
||||
383 | |||||
384 | 3 | return ($this->version == 'v3'); |
|||
385 | } |
||||
386 | } |
||||
387 |