Skip to content

Commit 18e5706

Browse files
authored
embed google recaptcha lib (#83)
embed google recaptcha lib with php 8.4 support
1 parent 664a2ce commit 18e5706

26 files changed

+2058
-54
lines changed

.github/workflows/code_checks.yaml

Lines changed: 5 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ jobs:
4343
runs-on: ubuntu-latest
4444
strategy:
4545
matrix:
46-
php: ['7.1', '7.2', '7.4', '8.0', '8.1', '8.2', '8.3']
46+
php: ['7.4', '8.0', '8.1', '8.2', '8.3', '8.4']
4747
symfony: ['^3.4', '^4.0', '^5.0', '^6.0', '^7.0']
4848
exclude:
4949
- symfony: ^3.4
@@ -52,26 +52,18 @@ jobs:
5252
php: 8.2
5353
- symfony: ^3.4
5454
php: 8.3
55+
- symfony: ^3.4
56+
php: 8.4
5557
- symfony: ^4.0
5658
php: 8.1
5759
- symfony: ^4.0
5860
php: 8.2
5961
- symfony: ^4.0
6062
php: 8.3
61-
- symfony: ^5.0
62-
php: 7.1
63-
- symfony: ^6.0
64-
php: 7.1
65-
- symfony: ^6.0
66-
php: 7.2
63+
- symfony: ^4.0
64+
php: 8.4
6765
- symfony: ^6.0
6866
php: 7.4
69-
- symfony: ^6.0
70-
php: 8.3
71-
- symfony: ^7.0
72-
php: 7.1
73-
- symfony: ^7.0
74-
php: 7.2
7567
- symfony: ^7.0
7668
php: 7.4
7769
- symfony: ^7.0

README.md

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,7 @@ With [composer](https://getcomposer.org), require:
2222

2323
`composer require karser/karser-recaptcha3-bundle`
2424

25-
You can quickly configure this bundle by using symfony/flex:
26-
- answer **no** for `google/recaptcha`
27-
- answer **yes** for `karser/karser-recaptcha3-bundle`
28-
![image](https://user-images.githubusercontent.com/1675033/73133604-d5a39a00-4033-11ea-9ef1-0fed12a8763b.png)
25+
You can quickly configure this bundle by using symfony/flex.
2926

3027
Configuration without symfony/flex:
3128
--------------------
@@ -265,7 +262,7 @@ App\Services\YourService:
265262
```php
266263
#App/Services/YourService.php
267264
268-
use ReCaptcha\ReCaptcha;
265+
use Karser\Recaptcha3Bundle\ReCaptcha\ReCaptcha;
269266
270267
class YourService {
271268
private $reCaptcha;

ReCaptcha/ReCaptcha.php

Lines changed: 274 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,274 @@
1+
<?php
2+
/**
3+
* This is a PHP library that handles calling reCAPTCHA.
4+
*
5+
* BSD 3-Clause License
6+
* @copyright (c) 2019, Google Inc.
7+
* @link https://www.google.com/recaptcha
8+
* All rights reserved.
9+
*
10+
* Redistribution and use in source and binary forms, with or without
11+
* modification, are permitted provided that the following conditions are met:
12+
* 1. Redistributions of source code must retain the above copyright notice, this
13+
* list of conditions and the following disclaimer.
14+
*
15+
* 2. Redistributions in binary form must reproduce the above copyright notice,
16+
* this list of conditions and the following disclaimer in the documentation
17+
* and/or other materials provided with the distribution.
18+
*
19+
* 3. Neither the name of the copyright holder nor the names of its
20+
* contributors may be used to endorse or promote products derived from
21+
* this software without specific prior written permission.
22+
*
23+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
24+
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
25+
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
26+
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
27+
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
28+
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
29+
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
30+
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
31+
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
32+
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
33+
*/
34+
35+
namespace Karser\Recaptcha3Bundle\ReCaptcha;
36+
37+
/**
38+
* reCAPTCHA client.
39+
*/
40+
class ReCaptcha
41+
{
42+
/**
43+
* Version of this client library.
44+
* @const string
45+
*/
46+
public const VERSION = 'php_1.3.0';
47+
48+
/**
49+
* URL for reCAPTCHA siteverify API
50+
* @const string
51+
*/
52+
public const SITE_VERIFY_URL = 'https://www.google.com/recaptcha/api/siteverify';
53+
54+
/**
55+
* Invalid JSON received
56+
* @const string
57+
*/
58+
public const E_INVALID_JSON = 'invalid-json';
59+
60+
/**
61+
* Could not connect to service
62+
* @const string
63+
*/
64+
public const E_CONNECTION_FAILED = 'connection-failed';
65+
66+
/**
67+
* Did not receive a 200 from the service
68+
* @const string
69+
*/
70+
public const E_BAD_RESPONSE = 'bad-response';
71+
72+
/**
73+
* Not a success, but no error codes received!
74+
* @const string
75+
*/
76+
public const E_UNKNOWN_ERROR = 'unknown-error';
77+
78+
/**
79+
* ReCAPTCHA response not provided
80+
* @const string
81+
*/
82+
public const E_MISSING_INPUT_RESPONSE = 'missing-input-response';
83+
84+
/**
85+
* Expected hostname did not match
86+
* @const string
87+
*/
88+
public const E_HOSTNAME_MISMATCH = 'hostname-mismatch';
89+
90+
/**
91+
* Expected APK package name did not match
92+
* @const string
93+
*/
94+
public const E_APK_PACKAGE_NAME_MISMATCH = 'apk_package_name-mismatch';
95+
96+
/**
97+
* Expected action did not match
98+
* @const string
99+
*/
100+
public const E_ACTION_MISMATCH = 'action-mismatch';
101+
102+
/**
103+
* Score threshold not met
104+
* @const string
105+
*/
106+
public const E_SCORE_THRESHOLD_NOT_MET = 'score-threshold-not-met';
107+
108+
/**
109+
* Challenge timeout
110+
* @const string
111+
*/
112+
public const E_CHALLENGE_TIMEOUT = 'challenge-timeout';
113+
114+
/**
115+
* Shared secret for the site.
116+
* @var string
117+
*/
118+
private $secret;
119+
120+
/**
121+
* Method used to communicate with service. Defaults to POST request.
122+
* @var RequestMethod
123+
*/
124+
private $requestMethod;
125+
126+
private $hostname;
127+
private $apkPackageName;
128+
private $action;
129+
private $threshold;
130+
private $timeoutSeconds;
131+
132+
/**
133+
* Create a configured instance to use the reCAPTCHA service.
134+
*
135+
* @param ?string $secret The shared key between your site and reCAPTCHA.
136+
* @param RequestMethod $requestMethod method used to send the request. Defaults to POST.
137+
* @throws \RuntimeException if $secret is invalid
138+
*/
139+
public function __construct(?string $secret, ?RequestMethod $requestMethod = null)
140+
{
141+
if (!is_string($secret)) {
142+
throw new \RuntimeException('The provided secret must be a string');
143+
}
144+
if ('' === $secret) {
145+
throw new \RuntimeException('No secret provided');
146+
}
147+
148+
$this->secret = $secret;
149+
$this->requestMethod = (is_null($requestMethod)) ? new RequestMethod\Post() : $requestMethod;
150+
}
151+
152+
/**
153+
* Calls the reCAPTCHA siteverify API to verify whether the user passes
154+
* CAPTCHA test and additionally runs any specified additional checks
155+
*
156+
* @param string $response The user response token provided by reCAPTCHA, verifying the user on your site.
157+
* @param string $remoteIp The end user's IP address.
158+
* @return Response Response from the service.
159+
*/
160+
public function verify(string $response, ?string $remoteIp = null)
161+
{
162+
// Discard empty solution submissions
163+
if ('' === $response) {
164+
$recaptchaResponse = new Response(false, array(self::E_MISSING_INPUT_RESPONSE));
165+
return $recaptchaResponse;
166+
}
167+
168+
$params = new RequestParameters($this->secret, $response, $remoteIp, self::VERSION);
169+
$rawResponse = $this->requestMethod->submit($params);
170+
$initialResponse = Response::fromJson($rawResponse);
171+
$validationErrors = array();
172+
173+
if (isset($this->hostname) && strcasecmp($this->hostname, $initialResponse->getHostname()) !== 0) {
174+
$validationErrors[] = self::E_HOSTNAME_MISMATCH;
175+
}
176+
177+
if (isset($this->apkPackageName) && strcasecmp($this->apkPackageName, $initialResponse->getApkPackageName()) !== 0) {
178+
$validationErrors[] = self::E_APK_PACKAGE_NAME_MISMATCH;
179+
}
180+
181+
if (isset($this->action) && strcasecmp($this->action, $initialResponse->getAction()) !== 0) {
182+
$validationErrors[] = self::E_ACTION_MISMATCH;
183+
}
184+
185+
if (isset($this->threshold) && $this->threshold > $initialResponse->getScore()) {
186+
$validationErrors[] = self::E_SCORE_THRESHOLD_NOT_MET;
187+
}
188+
189+
if (isset($this->timeoutSeconds)) {
190+
$challengeTs = strtotime($initialResponse->getChallengeTs());
191+
192+
if ($challengeTs > 0 && time() - $challengeTs > $this->timeoutSeconds) {
193+
$validationErrors[] = self::E_CHALLENGE_TIMEOUT;
194+
}
195+
}
196+
197+
if ([] === $validationErrors) {
198+
return $initialResponse;
199+
}
200+
201+
return new Response(
202+
false,
203+
array_merge($initialResponse->getErrorCodes(), $validationErrors),
204+
$initialResponse->getHostname(),
205+
$initialResponse->getChallengeTs(),
206+
$initialResponse->getApkPackageName(),
207+
$initialResponse->getScore(),
208+
$initialResponse->getAction()
209+
);
210+
}
211+
212+
/**
213+
* Provide a hostname to match against in verify()
214+
* This should be without a protocol or trailing slash, e.g. www.google.com
215+
*
216+
* @param string $hostname Expected hostname
217+
* @return ReCaptcha Current instance for fluent interface
218+
*/
219+
public function setExpectedHostname($hostname)
220+
{
221+
$this->hostname = $hostname;
222+
return $this;
223+
}
224+
225+
/**
226+
* Provide an APK package name to match against in verify()
227+
*
228+
* @param string $apkPackageName Expected APK package name
229+
* @return ReCaptcha Current instance for fluent interface
230+
*/
231+
public function setExpectedApkPackageName($apkPackageName)
232+
{
233+
$this->apkPackageName = $apkPackageName;
234+
return $this;
235+
}
236+
237+
/**
238+
* Provide an action to match against in verify()
239+
* This should be set per page.
240+
*
241+
* @param string $action Expected action
242+
* @return ReCaptcha Current instance for fluent interface
243+
*/
244+
public function setExpectedAction($action)
245+
{
246+
$this->action = $action;
247+
return $this;
248+
}
249+
250+
/**
251+
* Provide a threshold to meet or exceed in verify()
252+
* Threshold should be a float between 0 and 1 which will be tested as response >= threshold.
253+
*
254+
* @param float $threshold Expected threshold
255+
* @return ReCaptcha Current instance for fluent interface
256+
*/
257+
public function setScoreThreshold($threshold)
258+
{
259+
$this->threshold = floatval($threshold);
260+
return $this;
261+
}
262+
263+
/**
264+
* Provide a timeout in seconds to test against the challenge timestamp in verify()
265+
*
266+
* @param int $timeoutSeconds Expected hostname
267+
* @return ReCaptcha Current instance for fluent interface
268+
*/
269+
public function setChallengeTimeout($timeoutSeconds)
270+
{
271+
$this->timeoutSeconds = $timeoutSeconds;
272+
return $this;
273+
}
274+
}

ReCaptcha/RequestMethod.php

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
<?php
2+
/**
3+
* This is a PHP library that handles calling reCAPTCHA.
4+
*
5+
* BSD 3-Clause License
6+
* @copyright (c) 2019, Google Inc.
7+
* @link https://www.google.com/recaptcha
8+
* All rights reserved.
9+
*
10+
* Redistribution and use in source and binary forms, with or without
11+
* modification, are permitted provided that the following conditions are met:
12+
* 1. Redistributions of source code must retain the above copyright notice, this
13+
* list of conditions and the following disclaimer.
14+
*
15+
* 2. Redistributions in binary form must reproduce the above copyright notice,
16+
* this list of conditions and the following disclaimer in the documentation
17+
* and/or other materials provided with the distribution.
18+
*
19+
* 3. Neither the name of the copyright holder nor the names of its
20+
* contributors may be used to endorse or promote products derived from
21+
* this software without specific prior written permission.
22+
*
23+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
24+
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
25+
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
26+
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
27+
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
28+
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
29+
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
30+
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
31+
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
32+
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
33+
*/
34+
35+
namespace Karser\Recaptcha3Bundle\ReCaptcha;
36+
37+
/**
38+
* Method used to send the request to the service.
39+
*/
40+
interface RequestMethod
41+
{
42+
/**
43+
* Submit the request with the specified parameters.
44+
*
45+
* @param RequestParameters $params Request parameters
46+
* @return string Body of the reCAPTCHA response
47+
*/
48+
public function submit(RequestParameters $params);
49+
}

0 commit comments

Comments
 (0)