Passed
Branch master (7b9b82)
by Roberto
15:23
created

ReCaptchaBuilder::getCurlTimeout()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 1
c 0
b 0
f 0
nc 1
nop 0
dl 0
loc 3
rs 10
ccs 2
cts 2
cp 1
crap 1
1
<?php
2
/**
3
 * Copyright (c) 2017 - present
4
 * LaravelGoogleRecaptcha - ReCaptchaBuilder.php
5
 * author: Roberto Belotti - [email protected]
6
 * web : robertobelotti.com, github.com/biscolab
7
 * Initial version created on: 12/9/2018
8
 * MIT license: https://github.com/biscolab/laravel-recaptcha/blob/master/LICENSE
9
 */
10
11
namespace Biscolab\ReCaptcha;
12
13
use Exception;
14
use Illuminate\Support\Arr;
15
16
/**
17
 * Class ReCaptchaBuilder
18
 * @package Biscolab\ReCaptcha
19
 */
20
class ReCaptchaBuilder {
21
22
	/**
23
	 * @var string
24
	 */
25
	const DEFAULT_API_VERSION = 'v2';
26
27
	/**
28
	 * @var int
29
	 */
30
	const DEFAULT_CURL_TIMEOUT = 10;
31
32
	/**
33
	 * The Site key
34
	 * please visit https://developers.google.com/recaptcha/docs/start
35
	 * @var string
36
	 */
37
	protected $api_site_key;
38
39
	/**
40
	 * The Secret key
41
	 * please visit https://developers.google.com/recaptcha/docs/start
42
	 * @var string
43
	 */
44
	protected $api_secret_key;
45
46
	/**
47
	 * The chosen ReCAPTCHA version
48
	 * please visit https://developers.google.com/recaptcha/docs/start
49
	 * @var string
50
	 */
51
	protected $version;
52
53
	/**
54
	 * The curl timeout
55
	 * please visit https://curl.haxx.se/libcurl/c/CURLOPT_TIMEOUT.html
56
	 * @var int
57
	 */
58
	protected $curl_timeout;
59
60
	/**
61
	 * Whether is true the ReCAPTCHA is inactive
62
	 * @var boolean
63
	 */
64
	protected $skip_by_ip = false;
65
66
	/**
67
	 * The API request URI
68
	 */
69
	protected $api_url = 'https://www.google.com/recaptcha/api/siteverify';
70
71
	/**
72
	 * ReCaptchaBuilder constructor.
73
	 *
74
	 * @param string      $api_site_key
75
	 * @param string      $api_secret_key
76
	 * @param null|string $version
77
	 * @param int|null    $curl_timeout
78
	 */
79 17
	public function __construct(
80
		string $api_site_key,
81
		string $api_secret_key,
82
		?string $version = self::DEFAULT_API_VERSION,
83
		?int $curl_timeout = self::DEFAULT_CURL_TIMEOUT
84
	) {
85
86 17
		$this->setApiSiteKey($api_site_key);
87 17
		$this->setApiSecretKey($api_secret_key);
88 17
		$this->setVersion($version);
0 ignored issues
show
Bug introduced by
It seems like $version can also be of type null; however, parameter $version of Biscolab\ReCaptcha\ReCaptchaBuilder::setVersion() does only seem to accept string, 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 ignore-type  annotation

88
		$this->setVersion(/** @scrutinizer ignore-type */ $version);
Loading history...
89 17
		$this->setCurlTimeout($curl_timeout);
90 17
		$this->setSkipByIp($this->skipByIp());
91 17
	}
92
93
	/**
94
	 * @param string $api_site_key
95
	 *
96
	 * @return ReCaptchaBuilder
97
	 */
98 17
	public function setApiSiteKey(string $api_site_key): ReCaptchaBuilder {
99
100 17
		$this->api_site_key = $api_site_key;
101
102 17
		return $this;
103
	}
104
105
	/**
106
	 * @param string $api_secret_key
107
	 *
108
	 * @return ReCaptchaBuilder
109
	 */
110 17
	public function setApiSecretKey(string $api_secret_key): ReCaptchaBuilder {
111
112 17
		$this->api_secret_key = $api_secret_key;
113
114 17
		return $this;
115
	}
116
117
	/**
118
	 * @param int|null $curl_timeout
119
	 *
120
	 * @return ReCaptchaBuilder
121
	 */
122 17
	public function setCurlTimeout(?int $curl_timeout = null): ReCaptchaBuilder {
123
124 17
		if($curl_timeout === null) {
125 17
			$curl_timeout = config('recaptcha.curl_timeout', ReCaptchaBuilder::DEFAULT_CURL_TIMEOUT);
126
		}
127 17
		$this->curl_timeout = $curl_timeout;
128
129 17
		return $this;
130
	}
131
132
	/**
133
	 * @return int
134
	 */
135 3
	public function getCurlTimeout(): int {
136
137 3
		return $this->curl_timeout;
138
	}
139
140
	/**
141
	 * @param string $version
142
	 *
143
	 * @return ReCaptchaBuilder
144
	 */
145 17
	public function setVersion(string $version): ReCaptchaBuilder {
146
147 17
		$this->version = $version;
148
149 17
		return $this;
150
	}
151
152
	/**
153
	 * @return string
154
	 */
155 1
	public function getVersion(): string {
156
157 1
		return $this->version;
158
	}
159
160
	/**
161
	 * @param bool $skip_by_ip
162
	 *
163
	 * @return ReCaptchaBuilder
164
	 */
165 17
	public function setSkipByIp(bool $skip_by_ip): ReCaptchaBuilder {
166
167 17
		$this->skip_by_ip = $skip_by_ip;
168
169 17
		return $this;
170
	}
171
172
	/**
173
	 * @return array|mixed
174
	 */
175 17
	public function getIpWhitelist() {
176
177 17
		$whitelist = config('recaptcha.skip_ip', []);
178
179 17
		if (!is_array($whitelist)) {
180 2
			$whitelist = explode(',', $whitelist);
181
		}
182
183 17
		return $whitelist;
184
	}
185
186
	/**
187
	 * Checks whether the user IP address is among IPs "to be skipped"
188
	 *
189
	 * @return boolean
190
	 */
191 17
	public function skipByIp(): bool {
192
193 17
		return (in_array(request()->ip(), $this->getIpWhitelist()));
194
	}
195
196
	/**
197
	 * Write script HTML tag in you HTML code
198
	 * Insert before </head> tag
199
	 *
200
	 * @param string|null $formId
201
	 * @param array|null  $configuration
202
	 *
203
	 * @return string
204
	 * @throws Exception
205
	 */
206 6
	public function htmlScriptTagJsApi(?string $formId = '', ?array $configuration = []): string {
207
208 6
		if ($this->skip_by_ip) {
209
			return '';
210
		}
211
212 6
		switch ($this->version) {
213 6
			case 'v3':
214 5
				$html = "<script src=\"https://www.google.com/recaptcha/api.js?render={$this->api_site_key}\"></script>";
215 5
				break;
216
			default:
217 1
				$html = "<script src=\"https://www.google.com/recaptcha/api.js\" async defer></script>";
218
		}
219
220 6
		if ($this->version == 'invisible') {
221
			if (!$formId) {
222
				throw new Exception("formId required", 1);
223
			}
224
			$html .= '<script>
225
		       function biscolabLaravelReCaptcha(token) {
226
		         document.getElementById("' . $formId . '").submit();
227
		       }
228
		     </script>';
229
		}
230 6
		elseif ($this->version == 'v3') {
231
232 5
			$action = Arr::get($configuration, 'action', 'homepage');
233
234 5
			$js_custom_validation = Arr::get($configuration, 'custom_validation', '');
235
236
			// Check if set custom_validation. That function will override default fetch validation function
237 5
			if ($js_custom_validation) {
238
239 1
				$validate_function = ($js_custom_validation) ? "{$js_custom_validation}(token);" : '';
240
			}
241
			else {
242
243 4
				$js_then_callback = Arr::get($configuration, 'callback_then', '');
244 4
				$js_callback_catch = Arr::get($configuration, 'callback_catch', '');
245
246 4
				$js_then_callback = ($js_then_callback) ? "{$js_then_callback}(response)" : '';
247 4
				$js_callback_catch = ($js_callback_catch) ? "{$js_callback_catch}(err)" : '';
248
249
				$validate_function = "
250 4
                fetch('/" . config('recaptcha.default_validation_route',
251 4
						'biscolab-recaptcha/validate') . "?" . config('recaptcha.default_token_parameter_name',
252 4
						'token') . "=' + token, {
253
                    headers: {
254
                        \"X-Requested-With\": \"XMLHttpRequest\",
255
                        \"X-CSRF-TOKEN\": csrfToken.content
256
                    }
257
                })
258
                .then(function(response) {
259 4
                   	{$js_then_callback}
260
                })
261
                .catch(function(err) {
262 4
                    {$js_callback_catch}
263
                });";
264
			}
265
266
			$html .= "<script>
267
                    var csrfToken = document.head.querySelector('meta[name=\"csrf-token\"]');
268
                  grecaptcha.ready(function() {
269 5
                      grecaptcha.execute('{$this->api_site_key}', {action: '{$action}'}).then(function(token) {
270 5
                        {$validate_function}
271
                      });
272
                  });
273
		     </script>";
274
		}
275
276 6
		return $html;
277
	}
278
279
	/**
280
	 * @param array|null $configuration
281
	 *
282
	 * @return string
283
	 */
284 5
	public function htmlScriptTagJsApiV3(?array $configuration = []): string {
285
286 5
		return $this->htmlScriptTagJsApi('', $configuration);
287
	}
288
289
	/**
290
	 * Call out to reCAPTCHA and process the response
291
	 *
292
	 * @param string $response
293
	 *
294
	 * @return boolean|array
295
	 */
296 1
	public function validate($response) {
297
298 1
		if ($this->skip_by_ip) {
299
			if ($this->returnArray()) {
300
				// Add 'skip_by_ip' field to response
301
				return [
302
					'skip_by_ip' => true,
303
					'score'      => 0.9,
304
					'success'    => true
305
				];
306
			}
307
308
			return true;
309
		}
310
311 1
		$params = http_build_query([
312 1
			'secret'   => $this->api_secret_key,
313 1
			'remoteip' => request()->getClientIp(),
314 1
			'response' => $response,
315
		]);
316
317 1
		$url = $this->api_url . '?' . $params;
318
319 1
		if (function_exists('curl_version')) {
320 1
			$curl = curl_init($url);
321 1
			curl_setopt($curl, CURLOPT_HEADER, false);
0 ignored issues
show
Bug introduced by
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 ignore-type  annotation

321
			curl_setopt(/** @scrutinizer ignore-type */ $curl, CURLOPT_HEADER, false);
Loading history...
322 1
			curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
323 1
			curl_setopt($curl, CURLOPT_TIMEOUT, $this->curl_timeout);
324 1
			curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);
325 1
			$curl_response = curl_exec($curl);
0 ignored issues
show
Bug introduced by
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 ignore-type  annotation

325
			$curl_response = curl_exec(/** @scrutinizer ignore-type */ $curl);
Loading history...
326
		}
327
		else {
328
			$curl_response = file_get_contents($url);
329
		}
330
331 1
		if (is_null($curl_response) || empty($curl_response)) {
332
			if ($this->returnArray()) {
333
				// Add 'error' field to response
334
				return [
335
					'error'   => 'cURL response empty',
336
					'score'   => 0.1,
337
					'success' => false
338
				];
339
			}
340
341
			return false;
342
		}
343 1
		$response = json_decode(trim($curl_response), true);
344
345 1
		if ($this->returnArray()) {
346 1
			return $response;
347
		}
348
349
		return $response['success'];
350
351
	}
352
353
	/**
354
	 * @return bool
355
	 */
356 1
	protected function returnArray(): bool {
357
358 1
		return ($this->version == 'v3');
359
	}
360
}