Completed
Push — master ( f3b5aa...7b9b82 )
by Roberto
04:07 queued 11s
created

ReCaptchaBuilder::validate()   B

Complexity

Conditions 8
Paths 10

Size

Total Lines 54
Code Lines 32

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 18
CRAP Score 9.8645

Importance

Changes 6
Bugs 0 Features 1
Metric Value
cc 8
eloc 32
c 6
b 0
f 1
nc 10
nop 1
dl 0
loc 54
rs 8.1635
ccs 18
cts 26
cp 0.6923
crap 9.8645

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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
}