diff --git a/.github/workflows/php.yml b/.github/workflows/php.yml
index daffdd5..2044172 100644
--- a/.github/workflows/php.yml
+++ b/.github/workflows/php.yml
@@ -11,10 +11,12 @@ jobs:
strategy:
fail-fast: true
matrix:
- php: [8.0]
- laravel: [8.*]
+ php: [7.4, 8.0]
+ laravel: [7.*, 8.*]
dependency-version: [prefer-lowest, prefer-stable]
include:
+ - laravel: 7.*
+ testbench: ^5.18
- laravel: 8.*
testbench: ^6.14
@@ -22,7 +24,7 @@ jobs:
steps:
- name: Checkout
- uses: actions/checkout@v1
+ uses: actions/checkout@v2
- name: Setup PHP
uses: shivammathur/setup-php@v2
@@ -32,7 +34,7 @@ jobs:
coverage: xdebug
- name: Cache dependencies
- uses: actions/cache@v1
+ uses: actions/cache@v2
with:
path: ~/.composer/cache/files
key: ${{ runner.os }}-laravel-${{ matrix.laravel }}-php-${{ matrix.php }}-composer-${{ hashFiles('composer.json') }}
@@ -52,5 +54,5 @@ jobs:
COVERALLS_SERVICE_NAME: github
run: |
rm -rf composer.* vendor/
- composer require cedx/coveralls
- vendor/bin/coveralls build/logs/clover.xml
+ composer require php-coveralls/php-coveralls
+ vendor/bin/php-coveralls
diff --git a/README.md b/README.md
index 7cace20..707d044 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,6 @@

-[](https://packagist.org/packages/darkghosthunter/captchavel) [](https://packagist.org/packages/darkghosthunter/larapoke)   [](https://coveralls.io/github/DarkGhostHunter/Captchavel?branch=master) [](https://codeclimate.com/github/DarkGhostHunter/Captchavel/maintainability) [](https://github.com/laravel/octane)
+[](https://packagist.org/packages/darkghosthunter/captchavel) [](https://packagist.org/packages/darkghosthunter/larapoke)   [](https://coveralls.io/github/DarkGhostHunter/Captchavel?branch=master) [](https://codeclimate.com/github/DarkGhostHunter/Captchavel/maintainability) [](https://github.com/laravel/octane)
# Captchavel
@@ -14,8 +14,6 @@ It uses your Laravel HTTP Client and **HTTP/2**, making your app **fast**. You o
* [Installation](#installation)
* [Set Up](#set-up)
* [Usage](#usage)
- - [Checkbox, Invisible and Android challenges](#checkbox-invisible-and-android-challenges)
- - [Score driven interaction](#score-driven-interaction)
* [Frontend integration](#frontend-integration)
* [Advanced configuration](#advanced-configuration)
* [Testing with Captchavel](#testing-with-captchavel)
@@ -24,8 +22,8 @@ It uses your Laravel HTTP Client and **HTTP/2**, making your app **fast**. You o
## Requirements
-* Laravel 8.x
-* PHP 8.0
+* Laravel 7.x, 8.x, or later
+* PHP 7.4, 8.0 or later
> If you need support for old versions, consider sponsoring or donating.
@@ -39,22 +37,22 @@ composer require darkghosthunter/captchavel
## Set up
-Add the reCAPTCHA keys for your site to the environment file of your project. You can add each of them for reCAPTCHA v2 **checkbox**, **invisible**, **Android**, and **v3** (score).
+Add the reCAPTCHA keys for your site to the environment file of your project. You can add each of them for reCAPTCHA v2 **checkbox**, **invisible**, **Android**, and **score**.
If you don't have one, generate it in your [reCAPTCHA Admin panel](https://www.google.com/recaptcha/admin/).
```dotenv
-RECAPTCHA_V2_CHECKBOX_SECRET=6t5geA1UAAAAAN...
-RECAPTCHA_V2_CHECKBOX_KEY=6t5geA1UAAAAAN...
+RECAPTCHA_CHECKBOX_SECRET=6t5geA1UAAAAAN...
+RECAPTCHA_CHECKBOX_KEY=6t5geA1UAAAAAN...
-RECAPTCHA_V2_INVISIBLE_SECRET=6t5geA2UAAAAAN...
-RECAPTCHA_V2_INVISIBLE_KEY=6t5geA2UAAAAAN...
+RECAPTCHA_INVISIBLE_SECRET=6t5geA2UAAAAAN...
+RECAPTCHA_INVISIBLE_KEY=6t5geA2UAAAAAN...
-RECAPTCHA_V2_ANDROID_SECRET=6t5geA3UAAAAAN...
-RECAPTCHA_V2_ANDROID_KEY=6t5geA3UAAAAAN...
+RECAPTCHA_ANDROID_SECRET=6t5geA3UAAAAAN...
+RECAPTCHA_ANDROID_KEY=6t5geA3UAAAAAN...
-RECAPTCHA_V3_SECRET=6t5geA4UAAAAAN...
-RECAPTCHA_V3_KEY=6t5geA4UAAAAAN...
+RECAPTCHA_SCORE_SECRET=6t5geA4UAAAAAN...
+RECAPTCHA_SCORE_KEY=6t5geA4UAAAAAN...
```
This allows you to check different reCAPTCHA mechanisms using the same application, in different environments.
@@ -63,37 +61,38 @@ This allows you to check different reCAPTCHA mechanisms using the same applicati
## Usage
-After you integrate reCAPTCHA into your frontend or Android app, set the Captchavel middleware in the routes you want:
+Usage differs based on if you're using checkbox, invisible, or Android challenges, or the v3 score-driven challenge.
-* `recaptcha.v2` for Checkbox, Invisible and Android challenges.
-* `recaptcha.v3` for Score driven interaction.
+### Checkbox, invisible and Android challenges
-### Checkbox, Invisible and Android challenges
+After you integrate reCAPTCHA into your frontend or Android app, set the Captchavel middleware in the `POST` routes where a form with reCAPTCHA is submitted. The middleware will catch the `g-recaptcha-response` input and check if it's valid.
-Add the `recaptcha.v2` middleware to your `POST` routes. The middleware will catch the `g-recaptcha-response` input and check if it's valid.
-
-* `recaptcha.v2:checkbox` for explicitly rendered checkbox challenges.
-* `recaptcha.v2:invisible` for invisible challenges.
-* `recaptcha.v2:android` for Android app challenges.
+* `recaptcha:checkbox` for explicitly rendered checkbox challenges.
+* `recaptcha:invisible` for invisible challenges.
+* `recaptcha:android` for Android app challenges.
When the validation fails, the user will be redirected back to the form route, or a JSON response will be returned with the validation errors.
```php
-Route::post('login', 'LoginController@login')
- ->middleware('recaptcha.v2:checkbox');
+use App\Http\Controllers\Auth\LoginController;
+
+Route::post('login', [LoginController::class, 'login'])->middleware('recaptcha:checkbox');
```
-> You can change the input name from `g-recaptcha-response` to a custom using a second parameter, like `recaptcha.v2:checkbox,_recaptcha`.
+> You can change the input name from `g-recaptcha-response` to a custom using a second parameter, like `recaptcha.checkbox:my_input_name`.
+
+### Score-driven challenge
-### Score driven interaction
+The reCAPTCHA v3 middleware works differently from v2. This is a score-driven response is _always_ a success, but the challenge is scored between `0.0` and `1.0`. Robots will get lower scores, while human-like interaction will be higher.
-The reCAPTCHA v3 middleware works differently from v2. This is a score-driven challenge between `0.0` and `1.0` where robots will get lower scores than humans. The default threshold is `0.5`.
+The default threshold is `0.5`, which will set apart robots that score less than that.
-Simply add the `recaptcha.v3` middleware to your route:
+Simply add the `recaptcha.score` middleware to your route:
```php
-Route::post('comment', 'CommentController@store')
- ->middleware('recaptcha.v3');
+use App\Http\Controllers\CommentController;
+
+Route::post('comment', [CommentController::class, 'create'])->middleware('recaptcha.score');
```
Once the challenge has been received, you will have access to two methods from the Request instance: `isHuman()` and `isRobot()`, which return `true` or `false`:
@@ -107,7 +106,7 @@ public function store(Request $request, Post $post)
$comment = $post->comment()->make($request->only('body'));
- // Flag the comment as "moderated" if it was a written by robot.
+ // Flag the comment as "moderated" if it was a written by robot.
$comment->moderated = $request->isRobot();
$comment->save();
@@ -118,36 +117,38 @@ public function store(Request $request, Post $post)
#### Threshold, action and input name
-The middleware accepts three parameters in the following order:
+The middleware accepts three additional parameters in the following order:
1. Threshold: Values **above or equal** are considered human.
2. Action: The action name to optionally check against.
3. Input: The name of the reCAPTCHA input to verify.
```php
-Route::post('comment', 'CommentController@store')
- ->middleware('recaptcha.v3:0.7,login,custom-recaptcha-input');
+use App\Http\Controllers\CommentController;
+
+Route::post('comment', [CommentController::class, 'create'])
+ ->middleware('recaptcha.score:0.7,login,custom-recaptcha-input');
```
-> When checking the action name, ensure your Frontend action matches
+> When checking the action name, ensure your Frontend action matches .
-#### Faking robot and human scores
+#### Faking reCAPTCHA scores
-You can easily fake a reCAPTCHA v3 response in your local development by setting `CAPTCHAVEL_FAKE` to `true`.
+You can easily fake a reCAPTCHA response scores in your local development by setting `CAPTCHAVEL_FAKE` to `true`.
```dotenv
CAPTCHAVEL_FAKE=true
```
-Then, you can fake a low-score response by adding an `is_robot` a checkbox, respectively.
+This environment variable changes the reCAPTCHA Factory for a fake one, which will fake successful responses from reCAPTCHA, instead of resolving real challenges.
+
+From there, you can fake a robot or human response by adding an `is_robot` and returning `true` or `false`, respectively.
```blade
-
```
@@ -174,7 +175,7 @@ You can use the `captchavel()` helper to output the site key depending on the ch
Captchavel is intended to work out-of-the-box, but you can publish the configuration file for fine-tuning and additional reCAPTCHA verification.
```bash
-php artisan vendor:publish --provider="DarkGhostHunter\Captchavel\CaptchavelServiceProvider"
+php artisan vendor:publish --provider="DarkGhostHunter\Captchavel\CaptchavelServiceProvider" --tag="config"
```
You will get a config file with this array:
@@ -196,13 +197,23 @@ return [
### Enable Switch
+```php
+ env('CAPTCHAVEL_ENABLE', false),
+];
+```
+
+By default, Captchavel is disabled, so it doesn't check reCAPTCHA challenges. You can forcefully enable it with the `CAPTCHAVEL_ENABLE` environment variable.
+
```dotenv
CAPTCHAVEL_ENABLE=true
```
-The main switch to enable or disable Captchavel middleware. This can be handy to enable on some local environments to check real interaction using the included localhost test keys.
+This can be handy to enable on some local or development environments to check real interaction using the included localhost test keys, which only work on `localhost`.
-When switched off, the `g-recaptcha-response` won't be validated in the Request input, so you can safely disregard any frontend script or reCAPTCHA tokens or boxes.
+> When switched off, the reCAPTCHA challenge is not validated in the Request input, so you can safely disregard any frontend script or reCAPTCHA tokens or boxes.
### Fake responses
@@ -210,7 +221,7 @@ When switched off, the `g-recaptcha-response` won't be validated in the Request
CAPTCHAVEL_FAKE=true
```
-Setting this to true will allow your application to [fake v3-score responses from reCAPTCHA servers](#faking-robot-and-human-scores).
+If Captchavel is [enabled](#enable-switch), setting this to true will allow your application to [fake v3-score responses from reCAPTCHA servers](#faking-recaptcha-scores).
> This is automatically set to `true` when [running unit tests](#testing-with-captchavel).
@@ -249,13 +260,39 @@ return [
Here is the full array of [reCAPTCHA credentials](#set-up) to use depending on the version. Do not change the array unless you know what you're doing.
-## Testing with Captchavel
+## Testing Score with Captchavel
-When unit testing your application, this package [automatically fakes reCAPTCHA responses](#fake-responses).
+On testing, when Captchavel is disabled, routes set with the v2 middleware won't need to input the challenge in their body as it will be not verified.
-> When mocking requests, there is no need to add any reCAPTCHA token or secrets in your tests.
+On the other hand, reCAPTCHA v3 (score) responses can be [automatically faked](#fake-responses).
-When using reCAPTCHA v3 (score), you can fake a response made by a human or robot by simply using the `fakeHuman()` and `fakeRobot()` methods, which will score `1.0` or `0.0` respectively.
+To do that, you should [enable Captchavel](#enable-switch) on your tests, through the `.env.testing` environment file, or in [PHPUnit environment section](https://phpunit.readthedocs.io/en/9.5/configuration.html?highlight=environment#the-env-element). If you use another testing framework, refer to its documentation.
+
+```xml
+
+
+
+
+
+
+
+```
+
+Alternatively, you can change the configuration before your unit test:
+
+```php
+public function test_this_route()
+{
+ config()->set('captchavel.enable', true);
+ config()->set('captchavel.fake', true);
+
+ // ...
+}
+```
+
+> When faking challenges, there is no need to add any reCAPTCHA token or secrets in your tests.
+
+When using reCAPTCHA v3 (score), you can fake a response made by a human or robot by simply using the `fakeHuman()` and `fakeRobot()` methods, which will score `1.0` or `0.0` respectively for all subsequent requests.
```php
post('login', [
])->assertViewIs('login.2fa');
```
-Alternatively, `fakeScore()` method that will fake any score you set.
-
> Fake responses don't come with actions, hostnames or APK package names.
-### Events
-
-When a reCAPTCHA challenge is resolved, whatever result is received, the `ReCaptchaResponseReceived` event fires with the HTTP Request instance and the reCAPTCHA response.
+### Faking Scores manually
-### Using your own reCAPTCHA middleware
-
-You may want to create your own reCAPTCHA middleware. Instead of doing one from scratch, you can extend the `BaseReCaptchaMiddleware`.
+Alternatively, `fakeScore()` method will fake responses with any score you set.
```php
validateRequest($request, $input);
-
- $response = $this->retrieve($request, $input, 2, 'checkbox');
-
- if ($response->isInvalid()) {
- throw $this->validationException($input, 'Complete the reCAPTCHA challenge');
- }
-
- return $next($request);
- }
-}
+$this->post('comment', [
+ 'body' => 'This comment was made by a human',
+])->assertSee('Your comment has been posted!');
+
+// A robot should have its comment moderated.
+Captchavel::fakeScore(0.4);
+
+$this->post('comment', [
+ 'body' => 'Comment made by robot.',
+])->assertSee('Your comment will be reviewed before publishing.');
```
## Security
diff --git a/composer.json b/composer.json
index 108ef63..75793eb 100644
--- a/composer.json
+++ b/composer.json
@@ -17,22 +17,19 @@
}
],
"require": {
- "php": "^8.0",
+ "php": ">=7.4",
"ext-json": "*",
- "illuminate/support": "^8.0",
- "illuminate/http": "^8.0",
- "illuminate/routing": "^8.0",
- "illuminate/container": "^8.0",
- "illuminate/events": "^8.0",
- "guzzlehttp/guzzle": "^7.0"
+ "illuminate/support": "^7.0||^8.0",
+ "illuminate/http": "^7.0||^8.0",
+ "illuminate/routing": "^7.0||^8.0",
+ "illuminate/container": "^7.0||^8.0",
+ "illuminate/events": "^7.0||^8.0",
+ "guzzlehttp/guzzle": "^6.0||^7.0"
},
"require-dev": {
- "orchestra/testbench": "^6.14.0",
+ "orchestra/testbench": "^5.18||^6.14.0",
"phpunit/phpunit": "^9.5.2"
},
- "conflict": {
- "laravel/octane": "*"
- },
"autoload": {
"psr-4": {
"DarkGhostHunter\\Captchavel\\": "src"
diff --git a/config/captchavel.php b/config/captchavel.php
index d1ed7c7..3030348 100644
--- a/config/captchavel.php
+++ b/config/captchavel.php
@@ -69,23 +69,21 @@
*/
'credentials' => [
- 'v2' => [
- 'checkbox' => [
- 'secret' => env('RECAPTCHA_V2_CHECKBOX_SECRET', Captchavel::TEST_V2_SECRET),
- 'key' => env('RECAPTCHA_V2_CHECKBOX_KEY', Captchavel::TEST_V2_KEY),
- ],
- 'invisible' => [
- 'secret' => env('RECAPTCHA_V2_INVISIBLE_SECRET', Captchavel::TEST_V2_SECRET),
- 'key' => env('RECAPTCHA_V2_INVISIBLE_KEY', Captchavel::TEST_V2_KEY),
- ],
- 'android' => [
- 'secret' => env('RECAPTCHA_V2_ANDROID_SECRET', Captchavel::TEST_V2_SECRET),
- 'key' => env('RECAPTCHA_V2_ANDROID_KEY', Captchavel::TEST_V2_KEY),
+ Captchavel::CHECKBOX => [
+ 'secret' => env('RECAPTCHA_CHECKBOX_SECRET', Captchavel::TEST_V2_SECRET),
+ 'key' => env('RECAPTCHA_CHECKBOX_KEY', Captchavel::TEST_V2_KEY),
],
+ Captchavel::INVISIBLE => [
+ 'secret' => env('RECAPTCHA_INVISIBLE_SECRET', Captchavel::TEST_V2_SECRET),
+ 'key' => env('RECAPTCHA_INVISIBLE_KEY', Captchavel::TEST_V2_KEY),
+ ],
+ Captchavel::ANDROID => [
+ 'secret' => env('RECAPTCHA_ANDROID_SECRET', Captchavel::TEST_V2_SECRET),
+ 'key' => env('RECAPTCHA_ANDROID_KEY', Captchavel::TEST_V2_KEY),
],
- 'v3' => [
- 'secret' => env('RECAPTCHA_V3_SECRET'),
- 'key' => env('RECAPTCHA_V3_KEY'),
+ Captchavel::SCORE => [
+ 'secret' => env('RECAPTCHA_SCORE_SECRET'),
+ 'key' => env('RECAPTCHA_SCORE_KEY'),
],
],
];
diff --git a/phpunit.xml.dist b/phpunit.xml.dist
index 067cd56..abf7a10 100644
--- a/phpunit.xml.dist
+++ b/phpunit.xml.dist
@@ -1,21 +1,27 @@
-
-
-
- src/
-
-
-
-
-
-
-
-
-
- tests
-
-
-
-
-
+
+
+
+ src/
+
+
+
+
+
+
+
+
+
+ tests
+
+
+
+
+
+
+
+
diff --git a/src/Captchavel.php b/src/Captchavel.php
index 515c421..695f15f 100644
--- a/src/Captchavel.php
+++ b/src/Captchavel.php
@@ -2,14 +2,22 @@
namespace DarkGhostHunter\Captchavel;
-use LogicException;
+use DarkGhostHunter\Captchavel\Http\ReCaptchaResponse;
+use Illuminate\Container\Container;
+use Illuminate\Contracts\Config\Repository;
use Illuminate\Http\Client\Factory;
use Illuminate\Http\Client\Response;
-use Illuminate\Contracts\Config\Repository as Config;
-use DarkGhostHunter\Captchavel\Http\ReCaptchaResponse;
+use LogicException;
+use RuntimeException;
class Captchavel
{
+ // Constants to identify each reCAPTCHA service.
+ public const CHECKBOX = 'checkbox';
+ public const INVISIBLE = 'invisible';
+ public const ANDROID = 'android';
+ public const SCORE = 'score';
+
/**
* reCAPTCHA v2 secret for testing on "localhost".
*
@@ -38,99 +46,70 @@ class Captchavel
*/
public const INPUT = 'g-recaptcha-response';
- /**
- * The available reCAPTCHA v2 variants name.
- *
- * @var string
- */
- public const V2_VARIANTS = [
- 'checkbox', 'invisible', 'android',
- ];
-
/**
* Laravel HTTP Client factory.
*
- * @var \Illuminate\Http\Client\Factory|\Illuminate\Http\Client\PendingRequest
+ * @var \Illuminate\Http\Client\Factory
*/
- protected $httpFactory;
+ protected Factory $http;
/**
* Config Repository.
*
* @var \Illuminate\Contracts\Config\Repository
*/
- protected $config;
-
- /**
- * Secret to use with given challenge.
- *
- * @var string
- */
- protected $secret;
-
- /**
- * The Captchavel Response created from the reCAPTCHA response.
- *
- * @var null|\DarkGhostHunter\Captchavel\Http\ReCaptchaResponse
- */
- protected $response;
+ protected Repository $config;
/**
* Create a new Captchavel instance.
*
- * @param \Illuminate\Http\Client\Factory $httpFactory
+ * @param \Illuminate\Http\Client\Factory $http
* @param \Illuminate\Contracts\Config\Repository $config
*/
- public function __construct(Factory $httpFactory, Config $config)
+ public function __construct(Factory $http, Repository $config)
{
- $this->httpFactory = $httpFactory;
+ $this->http = $http;
$this->config = $config;
}
/**
- * Returns the Captchavel Response, if any.
+ * Resolves a reCAPTCHA challenge.
*
- * @return null|\DarkGhostHunter\Captchavel\Http\ReCaptchaResponse
- */
- public function getResponse()
- {
- return $this->response;
- }
-
- /**
- * Check if the a response was resolved from reCAPTCHA servers.
+ * @param string $challenge
+ * @param string $ip
+ * @param string $version
*
- * @return bool
+ * @return \DarkGhostHunter\Captchavel\Http\ReCaptchaResponse
*/
- public function isNotResolved()
+ public function getChallenge(string $challenge, string $ip, string $version): ReCaptchaResponse
{
- return $this->response === null;
+ $response = $this->send($challenge, $ip, $this->useCredentials($version))
+ ->setVersion($version)
+ ->setAsResolved();
+
+ Container::getInstance()->instance(ReCaptchaResponse::class, $response);
+
+ return $response;
}
/**
* Sets the correct credentials to use to retrieve the challenge results.
*
- * @param int $version
- * @param string|null $variant
- * @return $this
+ * @param string $mode
+ *
+ * @return string
*/
- public function useCredentials(int $version, string $variant = null)
+ protected function useCredentials(string $mode): string
{
- if ($version === 2) {
- if (! in_array($variant, static::V2_VARIANTS, true)) {
- throw new LogicException("The reCAPTCHA v2 variant must be [checkbox], [invisible] or [android].");
- }
- $this->secret = $this->config->get("captchavel.credentials.v2.{$variant}.secret");
- } elseif ($version === 3) {
- $this->secret = $this->config->get('captchavel.credentials.v3.secret');
+ if (!in_array($mode, static::getModes())) {
+ throw new LogicException('The reCAPTCHA mode must be: ' . implode(', ', static::getModes()));
}
- if (! $this->secret) {
- $name = 'v' . $version . ($variant ? '-' . $variant : '');
- throw new LogicException("The reCAPTCHA secret for [{$name}] doesn't exists.");
+ if (! $key = $this->config->get("captchavel.credentials.{$mode}.secret")) {
+ throw new RuntimeException("The reCAPTCHA secret for [{$mode}] doesn't exists");
}
- return $this;
+ return $key;
}
/**
@@ -138,17 +117,16 @@ public function useCredentials(int $version, string $variant = null)
*
* @param string $challenge
* @param string $ip
+ * @param string $secret
+ *
* @return \DarkGhostHunter\Captchavel\Http\ReCaptchaResponse
*/
- public function retrieve(string $challenge, string $ip)
+ protected function send(string $challenge, string $ip, string $secret): ReCaptchaResponse
{
- $response = $this->httpFactory->asForm()
+ $response = $this->http
+ ->asForm()
->withOptions(['version' => 2.0])
- ->post(static::RECAPTCHA_ENDPOINT, [
- 'secret' => $this->secret,
- 'response' => $challenge,
- 'remoteip' => $ip,
- ]);
+ ->post(static::RECAPTCHA_ENDPOINT, ['secret' => $secret, 'response' => $challenge, 'remoteip' => $ip]);
return $this->parse($response);
}
@@ -157,10 +135,21 @@ public function retrieve(string $challenge, string $ip)
* Parses the Response
*
* @param \Illuminate\Http\Client\Response $response
+ *
* @return \DarkGhostHunter\Captchavel\Http\ReCaptchaResponse
*/
- protected function parse(Response $response)
+ protected function parse(Response $response): ReCaptchaResponse
+ {
+ return new ReCaptchaResponse($response->json());
+ }
+
+ /**
+ * Checks if the mode is a valid mode name.
+ *
+ * @return array|string[]
+ */
+ protected static function getModes(): array
{
- return $this->response = new ReCaptchaResponse($response->json());
+ return [static::CHECKBOX, static::INVISIBLE, static::ANDROID, static::SCORE];
}
}
diff --git a/src/CaptchavelFake.php b/src/CaptchavelFake.php
index eae39dc..79b070e 100644
--- a/src/CaptchavelFake.php
+++ b/src/CaptchavelFake.php
@@ -7,76 +7,64 @@
class CaptchavelFake extends Captchavel
{
/**
- * Sets a fake response.
+ * Score to fake
*
- * @param \DarkGhostHunter\Captchavel\Http\ReCaptchaResponse $response
- * @return $this
+ * @var float|null
*/
- public function setResponse(ReCaptchaResponse $response)
- {
- $this->response = $response;
-
- return $this;
- }
+ public ?float $score = null;
/**
- * Sets the correct credentials to use to retrieve the challenge results.
+ * Resolves a reCAPTCHA challenge.
*
- * @param int $version
- * @param string|null $variant
- * @return $this
- */
- public function useCredentials(int $version, string $variant = null)
- {
- return $this;
- }
-
- /**
- * Retrieves the Response Challenge.
- *
- * @param string $challenge
+ * @param string|null $challenge
* @param string $ip
+ * @param string $version
+ *
* @return \DarkGhostHunter\Captchavel\Http\ReCaptchaResponse
*/
- public function retrieve(?string $challenge, string $ip)
+ public function getChallenge(?string $challenge = null, string $ip, string $version): ReCaptchaResponse
{
- return $this->response;
+ return (new ReCaptchaResponse(
+ [
+ 'success' => true,
+ 'action' => null,
+ 'hostname' => null,
+ 'apk_package_name' => null,
+ 'challenge_ts' => now()->toAtomString(),
+ 'score' => $this->score,
+ ]
+ ))->setVersion(Captchavel::SCORE)->setAsResolved();
}
/**
- * Makes the fake Captchavel response with a fake score.
+ * Adds a fake score to return as a reCAPTCHA response.
+ *
+ * @param float $score
*
- * @param float $score
- * @return $this
+ * @return void
*/
- public function fakeScore(float $score)
+ public function fakeScore(float $score): void
{
- return $this->setResponse(new ReCaptchaResponse([
- 'success' => true,
- 'score' => $score,
- 'action' => null,
- 'hostname' => null,
- 'apk_package_name' => null,
- ]));
+ $this->score = $score;
}
/**
* Makes a fake Captchavel response made by a robot with "0" score.
*
- * @return $this
+ * @return void
*/
- public function fakeRobot()
+ public function fakeRobots(): void
{
- return $this->fakeScore(0);
+ $this->score = 0;
}
/**
* Makes a fake Captchavel response made by a human with "1.0" score.
*
- * @return $this
+ * @return void
*/
- public function fakeHuman()
+ public function fakeHumans(): void
{
- return $this->fakeScore(1);
+ $this->score = 1.0;
}
}
diff --git a/src/CaptchavelServiceProvider.php b/src/CaptchavelServiceProvider.php
index f44df30..4a81119 100644
--- a/src/CaptchavelServiceProvider.php
+++ b/src/CaptchavelServiceProvider.php
@@ -2,12 +2,13 @@
namespace DarkGhostHunter\Captchavel;
+use DarkGhostHunter\Captchavel\Http\Middleware\VerifyReCaptchaV2;
+use DarkGhostHunter\Captchavel\Http\Middleware\VerifyReCaptchaV3;
+use DarkGhostHunter\Captchavel\Http\ReCaptchaResponse;
+use Illuminate\Contracts\Config\Repository;
use Illuminate\Http\Request;
use Illuminate\Routing\Router;
use Illuminate\Support\ServiceProvider;
-use Illuminate\Contracts\Config\Repository;
-use DarkGhostHunter\Captchavel\Http\Middleware\VerifyReCaptchaV2;
-use DarkGhostHunter\Captchavel\Http\Middleware\VerifyReCaptchaV3;
class CaptchavelServiceProvider extends ServiceProvider
{
@@ -20,7 +21,11 @@ public function register()
{
$this->mergeConfigFrom(__DIR__.'/../config/captchavel.php', 'captchavel');
+ // This is our new factory of reCAPTCHA responses.
$this->app->singleton(Captchavel::class);
+
+ // Bind an empty response which by default is never resolved.
+ $this->app->singleton(ReCaptchaResponse::class);
}
/**
@@ -33,17 +38,15 @@ public function register()
public function boot(Router $router, Repository $config)
{
if ($this->app->runningInConsole()) {
- $this->publishes([
- __DIR__.'/../config/captchavel.php' => config_path('captchavel.php'),
- ], 'config');
+ $this->publishes([__DIR__.'/../config/captchavel.php' => config_path('captchavel.php')], 'config');
if ($this->app->runningUnitTests()) {
$config->set('captchavel.fake', true);
}
}
- $router->aliasMiddleware('recaptcha.v2', VerifyReCaptchaV2::class);
- $router->aliasMiddleware('recaptcha.v3', VerifyReCaptchaV3::class);
+ $router->aliasMiddleware('recaptcha', VerifyReCaptchaV2::class);
+ $router->aliasMiddleware('recaptcha.score', VerifyReCaptchaV3::class);
Request::macro('isRobot', [RequestMacro::class, 'isRobot']);
Request::macro('isHuman', [RequestMacro::class, 'isHuman']);
diff --git a/src/Events/ReCaptchaResponseReceived.php b/src/Events/ReCaptchaResponseReceived.php
deleted file mode 100644
index b64c440..0000000
--- a/src/Events/ReCaptchaResponseReceived.php
+++ /dev/null
@@ -1,35 +0,0 @@
-request = $request;
- $this->response = $response;
- }
-}
diff --git a/src/Facades/Captchavel.php b/src/Facades/Captchavel.php
index 5a3d9e3..860c40f 100644
--- a/src/Facades/Captchavel.php
+++ b/src/Facades/Captchavel.php
@@ -2,7 +2,6 @@
namespace DarkGhostHunter\Captchavel\Facades;
-use DarkGhostHunter\Captchavel\Captchavel as BaseCaptchavel;
use DarkGhostHunter\Captchavel\CaptchavelFake;
use Illuminate\Support\Facades\Facade;
@@ -16,9 +15,9 @@ class Captchavel extends Facade
*
* @return string
*/
- protected static function getFacadeAccessor()
+ protected static function getFacadeAccessor(): string
{
- return BaseCaptchavel::class;
+ return \DarkGhostHunter\Captchavel\Captchavel::class;
}
/**
@@ -26,13 +25,15 @@ protected static function getFacadeAccessor()
*
* @return \DarkGhostHunter\Captchavel\CaptchavelFake
*/
- public static function fake()
+ public static function fake(): CaptchavelFake
{
- if (static::$resolvedInstance instanceof CaptchavelFake) {
- return static::$resolvedInstance;
+ $instance = static::getFacadeRoot();
+
+ if ($instance instanceof CaptchavelFake) {
+ return $instance;
}
- static::swap($fake = static::$app->make(CaptchavelFake::class));
+ static::swap($fake = static::getFacadeApplication()->make(CaptchavelFake::class));
return $fake;
}
@@ -41,30 +42,31 @@ public static function fake()
* Makes the fake Captchavel response with a fake score.
*
* @param float $score
- * @return \DarkGhostHunter\Captchavel\CaptchavelFake
+ *
+ * @return void
*/
public static function fakeScore(float $score)
{
- return static::fake()->fakeScore($score);
+ static::fake()->fakeScore($score);
}
/**
* Makes a fake Captchavel response made by a robot with "0" score.
*
- * @return \DarkGhostHunter\Captchavel\CaptchavelFake
+ * @return void
*/
public static function fakeRobot()
{
- return static::fake()->fakeRobot();
+ static::fake()->fakeRobots();
}
/**
* Makes a fake Captchavel response made by a human with "1.0" score.
*
- * @return \DarkGhostHunter\Captchavel\CaptchavelFake
+ * @return void
*/
public static function fakeHuman()
{
- return static::fake()->fakeHuman();
+ static::fake()->fakeHumans();
}
}
diff --git a/src/Http/Middleware/BaseReCaptchaMiddleware.php b/src/Http/Middleware/BaseReCaptchaMiddleware.php
deleted file mode 100644
index d4e04d4..0000000
--- a/src/Http/Middleware/BaseReCaptchaMiddleware.php
+++ /dev/null
@@ -1,174 +0,0 @@
-captchavel = $captchavel;
- $this->config = $config;
- }
-
- /**
- * Determines if the reCAPTCHA verification should be enabled.
- *
- * @return bool
- */
- protected function isEnabled()
- {
- return $this->config->get('captchavel.enable');
- }
-
- /**
- * Check if the reCAPTCHA response can be faked on-demand.
- *
- * @return bool
- */
- protected function isFake()
- {
- return $this->config->get('captchavel.fake');
- }
-
- /**
- * Check if the reCAPTCHA response must be real.
- *
- * @return bool
- */
- protected function isReal()
- {
- return ! $this->isFake();
- }
-
- /**
- * Validate if this Request has the reCAPTCHA challenge string.
- *
- * @param \Illuminate\Http\Request $request
- * @param string $name
- * @return void
- * @throws \Illuminate\Validation\ValidationException
- */
- protected function validateRequest($request, string $name)
- {
- if (! is_string($request->get($name))) {
- throw $this->validationException($name, 'The reCAPTCHA challenge is missing or has not been completed.');
- }
- }
-
- /**
- * Retrieves the Captchavel response from reCAPTCHA servers.
- *
- * @param \Illuminate\Http\Request $request
- * @param string $input
- * @param int $version
- * @param string|null $variant
- * @return \DarkGhostHunter\Captchavel\Http\ReCaptchaResponse
- */
- protected function retrieve(Request $request, string $input, int $version, string $variant = null)
- {
- return $this->captchavel
- ->useCredentials($version, $variant)
- ->retrieve($request->input($input), $request->ip());
- }
-
- /**
- * Fakes a v3 reCAPTCHA response.
- *
- * @param \Illuminate\Http\Request $request
- * @return void
- */
- protected function fakeResponseScore($request)
- {
- // We will first check if the Captchavel instance was not already faked. If it has been,
- // we will hands out the control to the developer. Otherwise, we will manually create a
- // fake Captchavel instance and look for the "is_robot" parameter to set a fake score.
- if ($this->captchavel instanceof Captchavel && $this->captchavel->isNotResolved()) {
- $this->captchavel = $request->has('is_robot') ?
- CaptchavelFacade::fakeRobot() :
- CaptchavelFacade::fakeHuman();
- }
- }
-
- /**
- * Validate the Hostname and APK name from the response.
- *
- * @param \DarkGhostHunter\Captchavel\Http\ReCaptchaResponse $response
- * @param string $input
- * @param string|null $action
- * @throws \Illuminate\Validation\ValidationException
- */
- protected function validateResponse(ReCaptchaResponse $response, $input = Captchavel::INPUT, ?string $action = null)
- {
- if ($response->differentHostname($this->config->get('captchavel.hostname'))) {
- throw $this->validationException('hostname',
- "The hostname [{$response->hostname}] of the response is invalid.");
- }
-
- if ($response->differentApk($this->config->get('captchavel.apk_package_name'))) {
- throw $this->validationException('apk_package_name',
- "The apk_package_name [{$response->apk_package_name}] of the response is invalid.");
- }
-
- if ($response->differentAction($action)) {
- throw $this->validationException('action',
- "The action [{$response->action}] of the response is invalid.");
- }
-
- if ($response->isInvalid()) {
- throw $this->validationException($input,
- "The reCAPTCHA challenge is invalid or was not completed.");
- }
- }
-
- /**
- * Creates a new Validation Exception instance.
- *
- * @param string $input
- * @param string $message
- * @return \Illuminate\Validation\ValidationException
- */
- protected function validationException($input, $message)
- {
- return ValidationException::withMessages([$input => trans($message)])->redirectTo(back()->getTargetUrl());
- }
-
- /**
- * Dispatch an event with the request and the Captchavel Response
- *
- * @param \Illuminate\Http\Request $request
- * @param \DarkGhostHunter\Captchavel\Http\ReCaptchaResponse $response
- */
- protected function dispatch(Request $request, ReCaptchaResponse $response)
- {
- event(new ReCaptchaResponseReceived($request, $response));
- }
-}
diff --git a/src/Http/Middleware/ChecksCaptchavelStatus.php b/src/Http/Middleware/ChecksCaptchavelStatus.php
new file mode 100644
index 0000000..1ebd5ee
--- /dev/null
+++ b/src/Http/Middleware/ChecksCaptchavelStatus.php
@@ -0,0 +1,26 @@
+config->get('captchavel.enable');
+ }
+
+ /**
+ * Check if the reCAPTCHA response should be faked on-demand.
+ *
+ * @return bool
+ */
+ protected function isFake(): bool
+ {
+ return $this->config->get('captchavel.fake');
+ }
+}
diff --git a/src/Http/Middleware/ValidatesRequestAndResponse.php b/src/Http/Middleware/ValidatesRequestAndResponse.php
new file mode 100644
index 0000000..9200afd
--- /dev/null
+++ b/src/Http/Middleware/ValidatesRequestAndResponse.php
@@ -0,0 +1,86 @@
+input($input);
+
+ if (!is_string($value) || blank($value)) {
+ throw $this->validationException($input, 'The reCAPTCHA challenge is missing or has not been completed.');
+ }
+ }
+
+ /**
+ * Creates a new Validation Exception instance.
+ *
+ * @param string $input
+ * @param string $message
+ *
+ * @return \Illuminate\Validation\ValidationException
+ */
+ protected function validationException(string $input, string $message): ValidationException
+ {
+ return ValidationException::withMessages([$input => trans($message)])->redirectTo(back()->getTargetUrl());
+ }
+
+
+ /**
+ * Validate the Hostname and APK name from the response.
+ *
+ * @param \DarkGhostHunter\Captchavel\Http\ReCaptchaResponse $response
+ * @param string $input
+ * @param string|null $action
+ *
+ * @throws \Illuminate\Validation\ValidationException
+ */
+ protected function validateResponse(
+ ReCaptchaResponse $response,
+ $input = Captchavel::INPUT,
+ ?string $action = null
+ ): void {
+ if ($response->isDifferentHostname($this->config->get('captchavel.hostname'))) {
+ throw $this->validationException(
+ 'hostname',
+ "The hostname [{$response->hostname}] of the response is invalid."
+ );
+ }
+
+ if ($response->isDifferentApk($this->config->get('captchavel.apk_package_name'))) {
+ throw $this->validationException(
+ 'apk_package_name',
+ "The apk_package_name [{$response->apk_package_name}] of the response is invalid."
+ );
+ }
+
+ if ($response->isDifferentAction($action)) {
+ throw $this->validationException(
+ 'action',
+ "The action [{$response->action}] of the response is invalid."
+ );
+ }
+
+ if ($response->isInvalid()) {
+ throw $this->validationException(
+ $input,
+ "The reCAPTCHA challenge is invalid or was not completed."
+ );
+ }
+ }
+}
diff --git a/src/Http/Middleware/VerifyReCaptchaV2.php b/src/Http/Middleware/VerifyReCaptchaV2.php
index 6990908..93ec60d 100644
--- a/src/Http/Middleware/VerifyReCaptchaV2.php
+++ b/src/Http/Middleware/VerifyReCaptchaV2.php
@@ -4,41 +4,61 @@
use Closure;
use DarkGhostHunter\Captchavel\Captchavel;
+use Illuminate\Config\Repository;
+use Illuminate\Http\Request;
-class VerifyReCaptchaV2 extends BaseReCaptchaMiddleware
+class VerifyReCaptchaV2
{
+ use ChecksCaptchavelStatus;
+ use ValidatesRequestAndResponse;
+
/**
- * Handle the incoming request.
+ * Captchavel connector.
*
- * @param \Illuminate\Http\Request $request
- * @param \Closure $next
- * @param string $variant
- * @param string $input
- * @return mixed
- * @throws \Illuminate\Validation\ValidationException
+ * @var \DarkGhostHunter\Captchavel\Captchavel|\DarkGhostHunter\Captchavel\CaptchavelFake
*/
- public function handle($request, Closure $next, $variant, $input = Captchavel::INPUT)
- {
- if ($this->isEnabled() && $this->isReal()) {
- $this->validateRequest($request, $input);
- $this->processChallenge($request, $variant, $input);
- }
+ protected Captchavel $captchavel;
- return $next($request);
+ /**
+ * Application Config repository.
+ *
+ * @var \Illuminate\Config\Repository
+ */
+ protected Repository $config;
+
+ /**
+ * BaseReCaptchaMiddleware constructor.
+ *
+ * @param \DarkGhostHunter\Captchavel\Captchavel $captchavel
+ * @param \Illuminate\Config\Repository $config
+ */
+ public function __construct(Captchavel $captchavel, Repository $config)
+ {
+ $this->config = $config;
+ $this->captchavel = $captchavel;
}
/**
- * Process a real challenge and response from reCAPTCHA servers.
+ * Handle the incoming request.
*
* @param \Illuminate\Http\Request $request
- * @param string $variant
+ * @param \Closure $next
+ * @param string $version
* @param string $input
+ *
+ * @return mixed
* @throws \Illuminate\Validation\ValidationException
*/
- protected function processChallenge($request, $variant, $input)
+ public function handle(Request $request, Closure $next, string $version, string $input = Captchavel::INPUT)
{
- $this->dispatch($request, $response = $this->retrieve($request, $input, 2, $variant));
+ if ($this->isEnabled() && !$this->isFake()) {
+ $this->validateRequest($request, $input);
+ $this->validateResponse(
+ $this->captchavel->getChallenge($request->input($input), $request->ip(), $version),
+ $input
+ );
+ }
- $this->validateResponse($response, $input);
+ return $next($request);
}
}
diff --git a/src/Http/Middleware/VerifyReCaptchaV3.php b/src/Http/Middleware/VerifyReCaptchaV3.php
index ad660a1..5b3f327 100644
--- a/src/Http/Middleware/VerifyReCaptchaV3.php
+++ b/src/Http/Middleware/VerifyReCaptchaV3.php
@@ -4,10 +4,44 @@
use Closure;
use DarkGhostHunter\Captchavel\Captchavel;
+use DarkGhostHunter\Captchavel\CaptchavelFake;
+use DarkGhostHunter\Captchavel\Facades\Captchavel as CaptchavelFacade;
use DarkGhostHunter\Captchavel\Http\ReCaptchaResponse;
+use Illuminate\Config\Repository;
+use Illuminate\Container\Container;
+use Illuminate\Http\Request;
-class VerifyReCaptchaV3 extends BaseReCaptchaMiddleware
+class VerifyReCaptchaV3
{
+ use ChecksCaptchavelStatus;
+ use ValidatesRequestAndResponse;
+
+ /**
+ * Captchavel connector.
+ *
+ * @var \DarkGhostHunter\Captchavel\Captchavel|\DarkGhostHunter\Captchavel\CaptchavelFake
+ */
+ protected Captchavel $captchavel;
+
+ /**
+ * Application Config repository.
+ *
+ * @var \Illuminate\Config\Repository
+ */
+ protected Repository $config;
+
+ /**
+ * BaseReCaptchaMiddleware constructor.
+ *
+ * @param \DarkGhostHunter\Captchavel\Captchavel $captchavel
+ * @param \Illuminate\Config\Repository $config
+ */
+ public function __construct(Captchavel $captchavel, Repository $config)
+ {
+ $this->config = $config;
+ $this->captchavel = $captchavel;
+ }
+
/**
* Handle the incoming request.
*
@@ -16,59 +50,83 @@ class VerifyReCaptchaV3 extends BaseReCaptchaMiddleware
* @param string|null $threshold
* @param string|null $action
* @param string $input
+ *
* @return mixed
* @throws \Illuminate\Validation\ValidationException
*/
- public function handle($request, Closure $next, $threshold = null, $action = null, $input = Captchavel::INPUT)
+ public function handle(Request $request,
+ Closure $next,
+ string $threshold = null,
+ string $action = null,
+ string $input = Captchavel::INPUT
+ )
{
if ($this->isEnabled()) {
- if ($this->isReal()) {
- $this->validateRequest($request, $input);
- } else {
+ if ($this->isFake()) {
$this->fakeResponseScore($request);
-
- // We will disable the action name since it will be verified if we don't null it.
- $action = null;
+ } else {
+ $this->validateRequest($request, $input);
}
- $this->processChallenge($request, $threshold, $action, $input);
+ $this->processChallenge($request, $input, $threshold, $action);
}
return $next($request);
}
+ /**
+ * Fakes a score reCAPTCHA response.
+ *
+ * @param \Illuminate\Http\Request $request
+ *
+ * @return void
+ */
+ protected function fakeResponseScore(Request $request): void
+ {
+ if (! $this->captchavel instanceof CaptchavelFake) {
+ $this->captchavel = CaptchavelFacade::fake();
+ }
+
+ // If the Captchavel has set an score to fake, use it, otherwise go default.
+ if ($this->captchavel->score === null) {
+ $request->filled('is_robot') ? $this->captchavel->fakeRobots() : $this->captchavel->fakeHumans();
+ }
+ }
+
/**
* Process the response from reCAPTCHA servers.
*
* @param \Illuminate\Http\Request $request
+ * @param string $input
* @param null|string $threshold
* @param null|string $action
- * @param string $input
+ *
* @throws \Illuminate\Validation\ValidationException
*/
- protected function processChallenge($request, $threshold, $action, $input)
+ protected function processChallenge(Request $request, string $input, ?string $threshold, ?string $action)
{
- $response = $this->retrieve($request, $input, 3);
-
- $response->setThreshold($this->normalizeThreshold($threshold));
-
- $this->dispatch($request, $response);
+ $response = $this->captchavel->getChallenge(
+ $request->input($input),
+ $request->ip(),
+ Captchavel::SCORE
+ )->setThreshold($this->normalizeThreshold($threshold));
$this->validateResponse($response, $input, $this->normalizeAction($action));
- // After we get the response, we will register the instance as a shared ("singleton").
- // Obviously we will set the threshold set by the developer or just use the default.
- // The Response should not be available until the middleware runs, so this is ok.
- app()->instance(ReCaptchaResponse::class, $response);
+ // After we get the response, we will register the instance as a shared
+ // "singleton" for the current request lifetime. Obviously we will set
+ // the threshold set by the developer or just use the config default.
+ Container::getInstance()->instance(ReCaptchaResponse::class, $response);
}
/**
* Normalize the threshold string.
*
- * @param string|null $threshold
- * @return array|float|mixed
+ * @param string|null $threshold
+ *
+ * @return float
*/
- protected function normalizeThreshold($threshold)
+ protected function normalizeThreshold(?string $threshold): float
{
return $threshold === 'null' ? $this->config->get('captchavel.threshold') : (float)$threshold;
}
@@ -77,9 +135,10 @@ protected function normalizeThreshold($threshold)
* Normalizes the action name, or returns null.
*
* @param null|string $action
+ *
* @return null|string
*/
- protected function normalizeAction($action)
+ protected function normalizeAction(?string $action) : ?string
{
return strtolower($action) === 'null' ? null : $action;
}
diff --git a/src/Http/ReCaptchaResponse.php b/src/Http/ReCaptchaResponse.php
index 51a1f66..c645041 100644
--- a/src/Http/ReCaptchaResponse.php
+++ b/src/Http/ReCaptchaResponse.php
@@ -2,14 +2,11 @@
namespace DarkGhostHunter\Captchavel\Http;
-use LogicException;
+use DarkGhostHunter\Captchavel\Captchavel;
use Illuminate\Support\Fluent;
+use RuntimeException;
/**
- * Class ReCaptchaResponse
- *
- * @package DarkGhostHunter\Captchavel\Http
- *
* @property-read null|string $hostname
* @property-read null|string $challenge_ts
* @property-read null|string $apk_package_name
@@ -20,12 +17,26 @@
*/
class ReCaptchaResponse extends Fluent
{
+ /**
+ * Default reCAPTCHA version.
+ *
+ * @var string|null
+ */
+ public ?string $version = null;
+
/**
* The threshold for reCAPTCHA v3.
*
* @var float
*/
- protected $threshold;
+ protected float $threshold = 1.0;
+
+ /**
+ * Check if the response from reCAPTCHA servers has been received.
+ *
+ * @var mixed
+ */
+ protected bool $resolved = false;
/**
* Sets the threshold to check the response.
@@ -33,23 +44,63 @@ class ReCaptchaResponse extends Fluent
* @param float $threshold
* @return $this
*/
- public function setThreshold(float $threshold)
+ public function setThreshold(float $threshold): ReCaptchaResponse
{
$this->threshold = $threshold;
return $this;
}
+ /**
+ * Sets the reCAPTCHA response as resolved.
+ *
+ * @return $this
+ */
+ public function setAsResolved(): ReCaptchaResponse
+ {
+ $this->resolved = true;
+
+ return $this;
+ }
+
+ /**
+ * Check if the reCAPTCHA response has been resolved.
+ *
+ * @return bool
+ */
+ public function isResolved(): bool
+ {
+ return $this->resolved;
+ }
+
+ /**
+ * Check if the reCAPTCHA response has not been resolved for the request.
+ *
+ * @return bool
+ */
+ public function isNotResolved():bool
+ {
+ return ! $this->isResolved();
+ }
+
/**
* Returns if the response was made by a Human.
*
* @throws \LogicException
* @return bool
*/
- public function isHuman()
+ public function isHuman(): bool
{
+ if ($this->isNotResolved()) {
+ throw new RuntimeException('There is no reCAPTCHA v3 response resolved for this request');
+ }
+
+ if ($this->version !== Captchavel::SCORE) {
+ throw new RuntimeException('This is not a reCAPTCHA v3 response');
+ }
+
if ($this->score === null) {
- throw new LogicException('This is not a reCAPTCHA v3 response, or the score is absent.');
+ throw new RuntimeException('This is reCAPTCHA v3 response has no score');
}
return $this->score >= $this->threshold;
@@ -60,7 +111,7 @@ public function isHuman()
*
* @return bool
*/
- public function isRobot()
+ public function isRobot(): bool
{
return ! $this->isHuman();
}
@@ -70,7 +121,7 @@ public function isRobot()
*
* @return bool
*/
- public function isValid()
+ public function isValid(): bool
{
return $this->success && empty($this->error_codes);
}
@@ -80,7 +131,7 @@ public function isValid()
*
* @return bool
*/
- public function isInvalid()
+ public function isInvalid(): bool
{
return ! $this->isValid();
}
@@ -91,7 +142,7 @@ public function isInvalid()
* @param string|null $string
* @return bool
*/
- public function differentHostname(?string $string)
+ public function isDifferentHostname(?string $string): bool
{
return $string && $this->hostname !== $string;
}
@@ -102,7 +153,7 @@ public function differentHostname(?string $string)
* @param string|null $string
* @return bool
*/
- public function differentApk(?string $string)
+ public function isDifferentApk(?string $string): bool
{
return $string && $this->apk_package_name !== $string;
}
@@ -113,7 +164,7 @@ public function differentApk(?string $string)
* @param null|string $action
* @return bool
*/
- public function differentAction(?string $action)
+ public function isDifferentAction(?string $action): bool
{
return $action && $this->action !== $action;
}
@@ -121,12 +172,67 @@ public function differentAction(?string $action)
/**
* Dynamically return an attribute as a property.
*
- * @param $name
- * @return null|mixed
+ * @param $key
+ * @return mixed
*/
- public function __get($name)
+ public function __get($key)
{
// Minor fix for getting the error codes
- return parent::__get($name === 'error_codes' ? 'error-codes' : $name);
+ return parent::__get($key === 'error_codes' ? 'error-codes' : $key);
+ }
+
+ /**
+ * Sets the version for this reCAPTCHA response.
+ *
+ * @param string $version
+ *
+ * @return $this
+ */
+ public function setVersion(string $version): ReCaptchaResponse
+ {
+ $this->version = $version;
+
+ return $this;
}
+
+ /**
+ * Checks if the reCAPTCHA challenge is for a given version.
+ *
+ * @return bool
+ */
+ public function isCheckbox(): bool
+ {
+ return $this->version === Captchavel::CHECKBOX;
+ }
+
+ /**
+ * Checks if the reCAPTCHA challenge is for a given version.
+ *
+ * @return bool
+ */
+ public function isInvisible(): bool
+ {
+ return $this->version === Captchavel::INVISIBLE;
+ }
+
+ /**
+ * Checks if the reCAPTCHA challenge is for a given version.
+ *
+ * @return bool
+ */
+ public function isAndroid(): bool
+ {
+ return $this->version === Captchavel::ANDROID;
+ }
+
+ /**
+ * Checks if the reCAPTCHA challenge is for a given version.
+ *
+ * @return bool
+ */
+ public function isScore(): bool
+ {
+ return $this->version === Captchavel::SCORE;
+ }
+
}
diff --git a/src/RequestMacro.php b/src/RequestMacro.php
index 3bdb0e2..c7d9edf 100644
--- a/src/RequestMacro.php
+++ b/src/RequestMacro.php
@@ -11,7 +11,7 @@ class RequestMacro
*
* @return bool
*/
- public static function isHuman()
+ public static function isHuman(): bool
{
return app(ReCaptchaResponse::class)->isHuman();
}
@@ -21,8 +21,8 @@ public static function isHuman()
*
* @return bool
*/
- public static function isRobot()
+ public static function isRobot(): bool
{
return ! static::isHuman();
}
-}
\ No newline at end of file
+}
diff --git a/src/helpers.php b/src/helpers.php
index 4487514..5e90acd 100644
--- a/src/helpers.php
+++ b/src/helpers.php
@@ -1,23 +1,20 @@
assertTrue(config('captchavel.fake'));
+ static::assertTrue(config('captchavel.fake'));
}
public function test_makes_fake_score()
{
Captchavel::fakeScore(0.3);
- Route::post('test', function (ReCaptchaResponse $response) {
+ $this->app['router']->post('test', function (ReCaptchaResponse $response) {
return [$response->score, $response->isRobot(), $response->isHuman()];
- })->middleware('recaptcha.v3:0.6');
+ })->middleware('recaptcha.score:0.6');
$this->post('test')->assertOk()->assertExactJson([0.3, true, false]);
}
@@ -41,9 +40,9 @@ public function test_makes_human_score_one()
{
Captchavel::fakeHuman();
- Route::post('test', function (ReCaptchaResponse $response) {
+ $this->app['router']->post('test', function (ReCaptchaResponse $response) {
return [$response->score, $response->isRobot(), $response->isHuman()];
- })->middleware('recaptcha.v3:0.6');
+ })->middleware('recaptcha.score:0.6');
$this->post('test')->assertOk()->assertExactJson([1.0, false, true]);
}
@@ -52,9 +51,9 @@ public function test_makes_robot_score_zero()
{
Captchavel::fakeRobot();
- Route::post('test', function (ReCaptchaResponse $response) {
+ $this->app['router']->post('test', function (ReCaptchaResponse $response) {
return [$response->score, $response->isRobot(), $response->isHuman()];
- })->middleware('recaptcha.v3:0.6');
+ })->middleware('recaptcha.score:0.6');
$this->post('test')->assertOk()->assertExactJson([0.0, true, false]);
}
diff --git a/tests/CaptchavelTest.php b/tests/CaptchavelTest.php
index 5aa9d31..cec4b58 100644
--- a/tests/CaptchavelTest.php
+++ b/tests/CaptchavelTest.php
@@ -1,14 +1,17 @@
shouldReceive('asForm')->withNoArgs()->times(3)->andReturnSelf();
$mock->shouldReceive('withOptions')->with(['version' => 2.0])->times(3)->andReturnSelf();
$mock->shouldReceive('post')
- ->with(Captchavel::RECAPTCHA_ENDPOINT, [
- 'secret' => Captchavel::TEST_V2_SECRET,
- 'response' => 'token',
- 'remoteip' => '127.0.0.1',
- ])
+ ->with(
+ Captchavel::RECAPTCHA_ENDPOINT,
+ [
+ 'secret' => Captchavel::TEST_V2_SECRET,
+ 'response' => 'token',
+ 'remoteip' => '127.0.0.1',
+ ]
+ )
->times(3)
- ->andReturn(new Response(new GuzzleResponse(200, ['Content-type' => 'application/json'], json_encode([
- 'success' => true,
- 'score' => 0.5,
- 'foo' => 'bar',
- ]))));
-
+ ->andReturn(
+ new Response(
+ new GuzzleResponse(
+ 200, ['Content-type' => 'application/json'], json_encode(
+ $array = [
+ 'success' => true,
+ 'score' => 0.5,
+ 'foo' => 'bar',
+ ]
+ )
+ )
+ )
+ );
+
+ /** @var \DarkGhostHunter\Captchavel\Captchavel $instance */
$instance = app(Captchavel::class);
- $this->assertNull($instance->getResponse());
-
- $checkbox = $instance->useCredentials(2, 'checkbox')->retrieve('token', '127.0.0.1');
- $this->assertSame($checkbox, $instance->getResponse());
- $this->assertInstanceOf(ReCaptchaResponse::class, $checkbox);
- $this->assertTrue($checkbox->success);
- $this->assertSame(0.5, $checkbox->score);
- $this->assertSame('bar', $checkbox->foo);
-
- $invisible = $instance->useCredentials(2, 'invisible')->retrieve('token', '127.0.0.1');
- $this->assertSame($invisible, $instance->getResponse());
- $this->assertInstanceOf(ReCaptchaResponse::class, $invisible);
- $this->assertTrue($invisible->success);
- $this->assertSame(0.5, $invisible->score);
- $this->assertSame('bar', $invisible->foo);
-
- $android = $instance->useCredentials(2, 'android')->retrieve('token', '127.0.0.1');
- $this->assertSame($android, $instance->getResponse());
- $this->assertInstanceOf(ReCaptchaResponse::class, $android);
- $this->assertTrue($android->success);
- $this->assertSame(0.5, $android->score);
- $this->assertSame('bar', $android->foo);
+ $checkbox = $instance->getChallenge('token', '127.0.0.1', 'checkbox');
+
+ static::assertTrue($checkbox->isResolved());
+ static::assertSame($checkbox->version, 'checkbox');
+ static::assertTrue($checkbox->success);
+ static::assertSame(0.5, $checkbox->score);
+ static::assertSame('bar', $checkbox->foo);
+
+ $invisible = $instance->getChallenge('token', '127.0.0.1', 'invisible');
+
+ static::assertTrue($invisible->isResolved());
+ static::assertSame($invisible->version, 'invisible');
+ static::assertTrue($invisible->success);
+ static::assertSame(0.5, $invisible->score);
+ static::assertSame('bar', $invisible->foo);
+
+ $android = $instance->getChallenge('token', '127.0.0.1', 'android');
+
+ static::assertTrue($android->isResolved());
+ static::assertSame($android->version, 'android');
+ static::assertTrue($android->success);
+ static::assertSame(0.5, $android->score);
+ static::assertSame('bar', $android->foo);
}
public function test_uses_v2_custom_credentials()
{
- config(['captchavel.credentials.v2' => [
- 'checkbox' => ['secret' => 'secret-checkbox'],
- 'invisible' => ['secret' => 'secret-invisible'],
- 'android' => ['secret' => 'secret-android'],
- ]]);
+ config(
+ [
+ 'captchavel.credentials' => [
+ 'checkbox' => ['secret' => 'secret-checkbox'],
+ 'invisible' => ['secret' => 'secret-invisible'],
+ 'android' => ['secret' => 'secret-android'],
+ ],
+ ]
+ );
$mock = $this->mock(Factory::class);
@@ -73,93 +93,118 @@ public function test_uses_v2_custom_credentials()
$mock->shouldReceive('withOptions')->with(['version' => 2.0])->times(3)->andReturnSelf();
$mock->shouldReceive('post')
- ->with(Captchavel::RECAPTCHA_ENDPOINT, [
- 'secret' => 'secret-checkbox',
- 'response' => 'token',
- 'remoteip' => '127.0.0.1',
- ])
+ ->with(
+ Captchavel::RECAPTCHA_ENDPOINT,
+ [
+ 'secret' => 'secret-checkbox',
+ 'response' => 'token',
+ 'remoteip' => '127.0.0.1',
+ ]
+ )
->once()
- ->andReturn(new Response(new GuzzleResponse(200, ['Content-type' => 'application/json'], json_encode([
- 'success' => true,
- 'score' => 0.5,
- 'foo' => 'bar',
- ]))));
+ ->andReturn(
+ new Response(
+ new GuzzleResponse(
+ 200, ['Content-type' => 'application/json'], json_encode(
+ [
+ 'success' => true,
+ 'score' => 0.5,
+ 'foo' => 'bar',
+ ]
+ )
+ )
+ )
+ );
$mock->shouldReceive('post')
- ->with(Captchavel::RECAPTCHA_ENDPOINT, [
- 'secret' => 'secret-invisible',
- 'response' => 'token',
- 'remoteip' => '127.0.0.1',
- ])
+ ->with(
+ Captchavel::RECAPTCHA_ENDPOINT,
+ [
+ 'secret' => 'secret-invisible',
+ 'response' => 'token',
+ 'remoteip' => '127.0.0.1',
+ ]
+ )
->once()
- ->andReturn(new Response(new GuzzleResponse(200, ['Content-type' => 'application/json'], json_encode([
- 'success' => true,
- 'score' => 0.5,
- 'foo' => 'bar',
- ]))));
+ ->andReturn(
+ new Response(
+ new GuzzleResponse(
+ 200, ['Content-type' => 'application/json'], json_encode(
+ [
+ 'success' => true,
+ 'score' => 0.5,
+ 'foo' => 'bar',
+ ]
+ )
+ )
+ )
+ );
$mock->shouldReceive('post')
- ->with(Captchavel::RECAPTCHA_ENDPOINT, [
- 'secret' => 'secret-android',
- 'response' => 'token',
- 'remoteip' => '127.0.0.1',
- ])
+ ->with(
+ Captchavel::RECAPTCHA_ENDPOINT,
+ [
+ 'secret' => 'secret-android',
+ 'response' => 'token',
+ 'remoteip' => '127.0.0.1',
+ ]
+ )
->once()
- ->andReturn(new Response(new GuzzleResponse(200, ['Content-type' => 'application/json'], json_encode([
- 'success' => true,
- 'score' => 0.5,
- 'foo' => 'bar',
- ]))));
+ ->andReturn(
+ new Response(
+ new GuzzleResponse(
+ 200, ['Content-type' => 'application/json'], json_encode(
+ [
+ 'success' => true,
+ 'score' => 0.5,
+ 'foo' => 'bar',
+ ]
+ )
+ )
+ )
+ );
$instance = app(Captchavel::class);
- $this->assertNull($instance->getResponse());
-
- $checkbox = $instance->useCredentials(2, 'checkbox')->retrieve('token', '127.0.0.1');
- $this->assertSame($checkbox, $instance->getResponse());
- $this->assertInstanceOf(ReCaptchaResponse::class, $checkbox);
- $this->assertTrue($checkbox->success);
- $this->assertSame(0.5, $checkbox->score);
- $this->assertSame('bar', $checkbox->foo);
-
- $invisible = $instance->useCredentials(2, 'invisible')->retrieve('token', '127.0.0.1');
- $this->assertSame($invisible, $instance->getResponse());
- $this->assertInstanceOf(ReCaptchaResponse::class, $invisible);
- $this->assertTrue($invisible->success);
- $this->assertSame(0.5, $invisible->score);
- $this->assertSame('bar', $invisible->foo);
-
- $android = $instance->useCredentials(2, 'android')->retrieve('token', '127.0.0.1');
- $this->assertSame($android, $instance->getResponse());
- $this->assertInstanceOf(ReCaptchaResponse::class, $android);
- $this->assertTrue($android->success);
- $this->assertSame(0.5, $android->score);
- $this->assertSame('bar', $android->foo);
+ static::assertEquals(
+ Captchavel::CHECKBOX,
+ $instance->getChallenge('token', '127.0.0.1', 'checkbox')->version
+ );
+ static::assertEquals(
+ Captchavel::INVISIBLE,
+ $instance->getChallenge('token', '127.0.0.1', 'invisible')->version
+ );
+ static::assertEquals(
+ Captchavel::ANDROID,
+ $instance->getChallenge('token', '127.0.0.1', 'android')->version
+ );
}
- public function test_exception_if_no_v3_secret_issued()
+ public function test_default_response_singleton_never_resolved()
{
- $this->expectException(LogicException::class);
- $this->expectExceptionMessage('The reCAPTCHA secret for [v3] doesn\'t exists.');
+ static::assertFalse(app(ReCaptchaResponse::class)->isResolved());
+ static::assertNull(app(ReCaptchaResponse::class)->version);
+ }
- $instance = app(Captchavel::class);
+ public function test_exception_if_no_v3_secret_issued()
+ {
+ $this->expectException(RuntimeException::class);
+ $this->expectExceptionMessage('The reCAPTCHA secret for [score] doesn\'t exists');
- $instance->useCredentials(3)->retrieve('token', '127.0.0.1');
+ app(Captchavel::class)->getChallenge('token', '127.0.0.1', 'score');
}
public function test_exception_when_invalid_credentials_issued()
{
$this->expectException(LogicException::class);
- $this->expectExceptionMessage('The reCAPTCHA v2 variant must be [checkbox], [invisible] or [android].');
-
- $instance = app(Captchavel::class);
+ $this->expectExceptionMessage('The reCAPTCHA mode must be: checkbox, invisible, android, score');
- $instance->useCredentials(2, 'invalid')->retrieve('token', '127.0.0.1');
+ app(Captchavel::class)->getChallenge('token', '127.0.0.1', 'invalid');
}
public function test_receives_v3_secret()
{
- config(['captchavel.credentials.v3.secret' => 'secret']);
+ config(['captchavel.credentials.score.secret' => 'secret']);
$mock = $this->mock(Factory::class);
@@ -180,14 +225,19 @@ public function test_receives_v3_secret()
$instance = app(Captchavel::class);
- $this->assertNull($instance->getResponse());
+ $score = $instance->getChallenge('token', '127.0.0.1', 'score');
- $score = $instance->useCredentials(3)->retrieve('token', '127.0.0.1');
+ static::assertEquals('score', $score->version);
+ static::assertTrue($score->isResolved());
+ static::assertTrue($score->success);
+ static::assertSame(0.5, $score->score);
+ static::assertSame('bar', $score->foo);
+ }
+
+ protected function tearDown(): void
+ {
+ Mockery::close();
- $this->assertSame($score, $instance->getResponse());
- $this->assertInstanceOf(ReCaptchaResponse::class, $score);
- $this->assertTrue($score->success);
- $this->assertSame(0.5, $score->score);
- $this->assertSame('bar', $score->foo);
+ parent::tearDown();
}
}
diff --git a/tests/HelperTest.php b/tests/HelperTest.php
index 63481a6..66ab6b6 100644
--- a/tests/HelperTest.php
+++ b/tests/HelperTest.php
@@ -2,9 +2,9 @@
namespace Tests;
-use LogicException;
-use Orchestra\Testbench\TestCase;
use DarkGhostHunter\Captchavel\Captchavel;
+use Orchestra\Testbench\TestCase;
+use RuntimeException;
class HelperTest extends TestCase
{
@@ -12,7 +12,7 @@ class HelperTest extends TestCase
public function test_exception_when_no_v3_key_loaded()
{
- $this->expectException(LogicException::class);
+ $this->expectException(RuntimeException::class);
$this->expectExceptionMessage('The reCAPTCHA site key for [3] doesn\'t exist.');
captchavel(3);
@@ -20,29 +20,23 @@ public function test_exception_when_no_v3_key_loaded()
public function test_retrieves_test_keys_by_default()
{
- $this->assertSame(Captchavel::TEST_V2_KEY, captchavel('checkbox'));
- $this->assertSame(Captchavel::TEST_V2_KEY, captchavel('invisible'));
- $this->assertSame(Captchavel::TEST_V2_KEY, captchavel('android'));
+ static::assertSame(Captchavel::TEST_V2_KEY, captchavel('checkbox'));
+ static::assertSame(Captchavel::TEST_V2_KEY, captchavel('invisible'));
+ static::assertSame(Captchavel::TEST_V2_KEY, captchavel('android'));
}
public function test_retrieves_secrets()
{
- config(['captchavel.credentials.v2' => [
+ config(['captchavel.credentials' => [
'checkbox' => ['key' => 'key-checkbox'],
'invisible' => ['key' => 'key-invisible'],
'android' => ['key' => 'key-android'],
+ 'score' => ['key' => 'key-score'],
]]);
- config(['captchavel.credentials.v3' => [
- 'key' => 'key-score'
- ]]);
-
- $this->assertSame('key-checkbox', captchavel('checkbox'));
- $this->assertSame('key-invisible', captchavel('invisible'));
- $this->assertSame('key-android', captchavel('android'));
- $this->assertSame('key-score', captchavel('score'));
- $this->assertSame('key-score', captchavel('v3'));
- $this->assertSame('key-score', captchavel('3'));
- $this->assertSame('key-score', captchavel(3));
+ static::assertSame('key-checkbox', captchavel('checkbox'));
+ static::assertSame('key-invisible', captchavel('invisible'));
+ static::assertSame('key-android', captchavel('android'));
+ static::assertSame('key-score', captchavel('score'));
}
}
diff --git a/tests/Http/Middleware/ChallengeMiddlewareTest.php b/tests/Http/Middleware/ChallengeMiddlewareTest.php
index 4b6d62b..f23e34b 100644
--- a/tests/Http/Middleware/ChallengeMiddlewareTest.php
+++ b/tests/Http/Middleware/ChallengeMiddlewareTest.php
@@ -2,34 +2,39 @@
namespace Tests\Http\Middleware;
-use Tests\RegistersPackage;
-use Orchestra\Testbench\TestCase;
-use Illuminate\Support\Facades\Event;
-use Illuminate\Support\Facades\Route;
use DarkGhostHunter\Captchavel\Captchavel;
-use DarkGhostHunter\Captchavel\Http\ReCaptchaResponse;
use DarkGhostHunter\Captchavel\Events\ReCaptchaResponseReceived;
+use DarkGhostHunter\Captchavel\Http\ReCaptchaResponse;
+use Orchestra\Testbench\TestCase;
+use Tests\RegistersPackage;
class ChallengeMiddlewareTest extends TestCase
{
use RegistersPackage;
use UsesRoutesWithMiddleware;
- protected function setUp() : void
+ protected function setUp(): void
{
- $this->afterApplicationCreated(function () {
- $this->createsRoutes();
- config(['captchavel.fake' => false]);
- });
+ $this->afterApplicationCreated(
+ function () {
+ $this->createsRoutes();
+ config(['captchavel.fake' => false]);
+ }
+ );
parent::setUp();
}
public function test_exception_if_no_challenge_specified()
{
- Route::post('test', function () {
- return 'ok';
- })->middleware('recaptcha.v2');
+ config()->set('app.debug', false);
+
+ $this->app['router']->post(
+ 'test',
+ function () {
+ return 'ok';
+ }
+ )->middleware('recaptcha');
$this->post('test')->assertStatus(500);
@@ -40,20 +45,28 @@ public function test_bypass_if_not_enabled()
{
config(['captchavel.enable' => false]);
- $event = Event::fake();
-
- $this->mock(Captchavel::class)->shouldNotReceive('useCredentials', 'retrieve');
+ $this->mock(Captchavel::class)->shouldNotReceive('resolve');
$this->post('v2/checkbox')->assertOk();
$this->post('v2/invisible')->assertOk();
$this->post('v2/android')->assertOk();
+ }
+ public function test_success_when_enabled_and_fake()
+ {
+ config(['captchavel.enable' => true]);
+ config(['captchavel.fake' => true]);
- $event->assertNotDispatched(ReCaptchaResponseReceived::class);
+ $this->post('v2/checkbox')->assertOk();
+ $this->post('v2/checkbox/input_bar')->assertOk();
+ $this->post('v2/invisible')->assertOk();
+ $this->post('v2/invisible/input_bar')->assertOk();
+ $this->post('v2/android')->assertOk();
+ $this->post('v2/android/input_bar')->assertOk();
}
- public function test_fakes_success()
+ public function test_success_when_disabled()
{
- config(['captchavel.fake' => true]);
+ config(['captchavel.enable' => false]);
$this->post('v2/checkbox')->assertOk();
$this->post('v2/checkbox/input_bar')->assertOk();
@@ -65,75 +78,51 @@ public function test_fakes_success()
public function test_validates_if_real()
{
- $event = Event::fake();
-
$mock = $this->mock(Captchavel::class);
- $mock->shouldReceive('useCredentials')->once()->with(2, 'checkbox')->andReturnSelf();
- $mock->shouldReceive('useCredentials')->once()->with(2, 'invisible')->andReturnSelf();
- $mock->shouldReceive('useCredentials')->once()->with(2, 'android')->andReturnSelf();
-
- $mock->shouldReceive('retrieve')
- ->times(3)
- ->with('token', '127.0.0.1')
- ->andReturn(new ReCaptchaResponse([
+ $response = new ReCaptchaResponse(
+ [
'success' => true,
'score' => 0.5,
- 'foo' => 'bar'
- ]));
-
- $this->post('v2/checkbox', [
- Captchavel::INPUT => 'token'
- ])->assertOk();
- $this->post('v2/invisible', [
- Captchavel::INPUT => 'token'
- ])->assertOk();
- $this->post('v2/android', [
- Captchavel::INPUT => 'token'
- ])->assertOk();
-
- $event->assertDispatchedTimes(ReCaptchaResponseReceived::class, 3);
+ 'foo' => 'bar',
+ ]
+ );
+
+ $mock->shouldReceive('getChallenge')->once()->with('token', '127.0.0.1', 'checkbox')->andReturn($response);
+ $mock->shouldReceive('getChallenge')->once()->with('token', '127.0.0.1', 'invisible')->andReturn($response);
+ $mock->shouldReceive('getChallenge')->once()->with('token', '127.0.0.1', 'android')->andReturn($response);
+
+ $this->post('v2/checkbox', [Captchavel::INPUT => 'token'])->assertOk();
+ $this->post('v2/invisible', [Captchavel::INPUT => 'token'])->assertOk();
+ $this->post('v2/android', [Captchavel::INPUT => 'token'])->assertOk();
}
public function test_uses_custom_input()
{
- $event = Event::fake();
-
$mock = $this->mock(Captchavel::class);
- $mock->shouldReceive('useCredentials')->once()->with(2, 'checkbox')->andReturnSelf();
- $mock->shouldReceive('useCredentials')->once()->with(2, 'invisible')->andReturnSelf();
- $mock->shouldReceive('useCredentials')->once()->with(2, 'android')->andReturnSelf();
-
- $mock->shouldReceive('retrieve')
- ->times(3)
- ->with('token', '127.0.0.1')
- ->andReturn(new ReCaptchaResponse([
+ $response = new ReCaptchaResponse(
+ [
'success' => true,
'score' => 0.5,
- 'foo' => 'bar'
- ]));
-
- $this->post('v2/checkbox/input_bar', [
- 'bar' => 'token'
- ])->assertOk();
- $this->post('v2/invisible/input_bar', [
- 'bar' => 'token'
- ])->assertOk();
- $this->post('v2/android/input_bar', [
- 'bar' => 'token'
- ])->assertOk();
-
- $event->assertDispatchedTimes(ReCaptchaResponseReceived::class, 3);
+ 'foo' => 'bar',
+ ]
+ );
+
+ $mock->shouldReceive('getChallenge')->once()->with('token', '127.0.0.1', 'checkbox')->andReturn($response);
+ $mock->shouldReceive('getChallenge')->once()->with('token', '127.0.0.1', 'invisible')->andReturn($response);
+ $mock->shouldReceive('getChallenge')->once()->with('token', '127.0.0.1', 'android')->andReturn($response);
+
+ $this->post('v2/checkbox/input_bar',['bar' => 'token'])->assertOk();
+ $this->post('v2/invisible/input_bar',['bar' => 'token'])->assertOk();
+ $this->post('v2/android/input_bar',['bar' => 'token'])->assertOk();
}
public function test_exception_when_token_absent()
{
- $event = Event::fake();
-
$mock = $this->mock(Captchavel::class);
- $mock->shouldNotReceive('useCredentials', 'retrieve');
+ $mock->shouldNotReceive('getChallenge');
$this->post('v2/checkbox')->assertRedirect('/');
$this->postJson('v2/checkbox')->assertJsonValidationErrors(Captchavel::INPUT);
@@ -148,26 +137,23 @@ public function test_exception_when_token_absent()
$this->postJson('v2/invisible/input_bar')->assertJsonValidationErrors('bar');
$this->post('v2/android/input_bar')->assertRedirect('/');
$this->postJson('v2/android/input_bar')->assertJsonValidationErrors('bar');
-
- $event->assertNotDispatched(ReCaptchaResponseReceived::class);
}
public function test_exception_when_response_invalid()
{
- $event = Event::fake();
-
$mock = $this->mock(Captchavel::class);
- $mock->shouldReceive('useCredentials')->twice()->with(2, 'checkbox')->andReturnSelf();
- $mock->shouldReceive('useCredentials')->twice()->with(2, 'invisible')->andReturnSelf();
- $mock->shouldReceive('useCredentials')->twice()->with(2, 'android')->andReturnSelf();
-
- $mock->shouldReceive('retrieve')
- ->times(6)
- ->with('token', '127.0.0.1')
- ->andReturn(new ReCaptchaResponse([
+ $response = new ReCaptchaResponse(
+ [
'success' => false,
- ]));
+ 'score' => 0.5,
+ 'foo' => 'bar',
+ ]
+ );
+
+ $mock->shouldReceive('getChallenge')->twice()->with('token', '127.0.0.1', 'checkbox')->andReturn($response);
+ $mock->shouldReceive('getChallenge')->twice()->with('token', '127.0.0.1', 'invisible')->andReturn($response);
+ $mock->shouldReceive('getChallenge')->twice()->with('token', '127.0.0.1', 'android')->andReturn($response);
$this->post('v2/checkbox', [Captchavel::INPUT => 'token'])->assertRedirect('/');
$this->postJson('v2/checkbox', [Captchavel::INPUT => 'token'])->assertJsonValidationErrors(Captchavel::INPUT);
@@ -175,83 +161,73 @@ public function test_exception_when_response_invalid()
$this->postJson('v2/invisible', [Captchavel::INPUT => 'token'])->assertJsonValidationErrors(Captchavel::INPUT);
$this->post('v2/android', [Captchavel::INPUT => 'token'])->assertRedirect('/');
$this->postJson('v2/android', [Captchavel::INPUT => 'token'])->assertJsonValidationErrors(Captchavel::INPUT);
-
- $event->assertDispatchedTimes(ReCaptchaResponseReceived::class, 6);
}
public function test_no_error_if_not_hostname_issued()
{
config(['captchavel.hostname' => null]);
- $event = Event::fake();
-
$mock = $this->mock(Captchavel::class);
- $mock->shouldReceive('useCredentials')->once()->with(2, 'checkbox')->andReturnSelf();
- $mock->shouldReceive('useCredentials')->once()->with(2, 'invisible')->andReturnSelf();
- $mock->shouldReceive('useCredentials')->once()->with(2, 'android')->andReturnSelf();
-
- $mock->shouldReceive('retrieve')
- ->times(3)
- ->with('token', '127.0.0.1')
- ->andReturn(new ReCaptchaResponse([
+ $response = new ReCaptchaResponse(
+ [
'success' => true,
- 'hostname' => 'foo'
- ]));
+ 'score' => 0.5,
+ 'foo' => 'bar',
+ ]
+ );
+
+ $mock->shouldReceive('getChallenge')->once()->with('token', '127.0.0.1', 'checkbox')->andReturn($response);
+ $mock->shouldReceive('getChallenge')->once()->with('token', '127.0.0.1', 'invisible')->andReturn($response);
+ $mock->shouldReceive('getChallenge')->once()->with('token', '127.0.0.1', 'android')->andReturn($response);
$this->post('v2/checkbox', [Captchavel::INPUT => 'token'])->assertOk();
$this->post('v2/invisible', [Captchavel::INPUT => 'token'])->assertOk();
$this->post('v2/android', [Captchavel::INPUT => 'token'])->assertOk();
-
- $event->assertDispatchedTimes(ReCaptchaResponseReceived::class, 3);
}
public function test_no_error_if_not_hostname_same()
{
config(['captchavel.hostname' => 'foo']);
- $event = Event::fake();
-
$mock = $this->mock(Captchavel::class);
- $mock->shouldReceive('useCredentials')->once()->with(2, 'checkbox')->andReturnSelf();
- $mock->shouldReceive('useCredentials')->once()->with(2, 'invisible')->andReturnSelf();
- $mock->shouldReceive('useCredentials')->once()->with(2, 'android')->andReturnSelf();
-
- $mock->shouldReceive('retrieve')
- ->times(3)
- ->with('token', '127.0.0.1')
- ->andReturn(new ReCaptchaResponse([
+ $response = new ReCaptchaResponse(
+ [
'success' => true,
- 'hostname' => 'foo'
- ]));
+ 'score' => 0.5,
+ 'foo' => 'bar',
+ 'hostname' => 'foo',
+ ]
+ );
+
+ $mock->shouldReceive('getChallenge')->once()->with('token', '127.0.0.1', 'checkbox')->andReturn($response);
+ $mock->shouldReceive('getChallenge')->once()->with('token', '127.0.0.1', 'invisible')->andReturn($response);
+ $mock->shouldReceive('getChallenge')->once()->with('token', '127.0.0.1', 'android')->andReturn($response);
$this->post('v2/checkbox', [Captchavel::INPUT => 'token'])->assertOk();
$this->post('v2/invisible', [Captchavel::INPUT => 'token'])->assertOk();
$this->post('v2/android', [Captchavel::INPUT => 'token'])->assertOk();
-
- $event->assertDispatchedTimes(ReCaptchaResponseReceived::class, 3);
}
public function test_exception_if_hostname_not_equal()
{
config(['captchavel.hostname' => 'bar']);
- $event = Event::fake();
-
$mock = $this->mock(Captchavel::class);
- $mock->shouldReceive('useCredentials')->twice()->with(2, 'checkbox')->andReturnSelf();
- $mock->shouldReceive('useCredentials')->twice()->with(2, 'invisible')->andReturnSelf();
- $mock->shouldReceive('useCredentials')->twice()->with(2, 'android')->andReturnSelf();
-
- $mock->shouldReceive('retrieve')
- ->times(6)
- ->with('token', '127.0.0.1')
- ->andReturn(new ReCaptchaResponse([
+ $response = new ReCaptchaResponse(
+ [
'success' => true,
- 'hostname' => 'foo'
- ]));
+ 'score' => 0.5,
+ 'foo' => 'bar',
+ 'hostname' => 'foo',
+ ]
+ );
+
+ $mock->shouldReceive('getChallenge')->twice()->with('token', '127.0.0.1', 'checkbox')->andReturn($response);
+ $mock->shouldReceive('getChallenge')->twice()->with('token', '127.0.0.1', 'invisible')->andReturn($response);
+ $mock->shouldReceive('getChallenge')->twice()->with('token', '127.0.0.1', 'android')->andReturn($response);
$this->post('v2/checkbox', [Captchavel::INPUT => 'token'])->assertRedirect('/');
$this->postJson('v2/checkbox', [Captchavel::INPUT => 'token'])->assertJsonValidationErrors('hostname');
@@ -259,83 +235,74 @@ public function test_exception_if_hostname_not_equal()
$this->postJson('v2/invisible', [Captchavel::INPUT => 'token'])->assertJsonValidationErrors('hostname');
$this->post('v2/android', [Captchavel::INPUT => 'token'])->assertRedirect('/');
$this->postJson('v2/android', [Captchavel::INPUT => 'token'])->assertJsonValidationErrors('hostname');
-
- $event->assertDispatchedTimes(ReCaptchaResponseReceived::class, 6);
}
public function test_no_error_if_no_apk_issued()
{
config(['captchavel.apk_package_name' => null]);
- $event = Event::fake();
-
$mock = $this->mock(Captchavel::class);
- $mock->shouldReceive('useCredentials')->once()->with(2, 'checkbox')->andReturnSelf();
- $mock->shouldReceive('useCredentials')->once()->with(2, 'invisible')->andReturnSelf();
- $mock->shouldReceive('useCredentials')->once()->with(2, 'android')->andReturnSelf();
-
- $mock->shouldReceive('retrieve')
- ->times(3)
- ->with('token', '127.0.0.1')
- ->andReturn(new ReCaptchaResponse([
+ $response = new ReCaptchaResponse(
+ [
'success' => true,
- 'apk_package_name' => 'foo'
- ]));
+ 'score' => 0.5,
+ 'foo' => 'bar',
+ 'apk_package_name' => 'foo',
+ ]
+ );
+
+ $mock->shouldReceive('getChallenge')->once()->with('token', '127.0.0.1', 'checkbox')->andReturn($response);
+ $mock->shouldReceive('getChallenge')->once()->with('token', '127.0.0.1', 'invisible')->andReturn($response);
+ $mock->shouldReceive('getChallenge')->once()->with('token', '127.0.0.1', 'android')->andReturn($response);
$this->post('v2/checkbox', [Captchavel::INPUT => 'token'])->assertOk();
$this->post('v2/invisible', [Captchavel::INPUT => 'token'])->assertOk();
$this->post('v2/android', [Captchavel::INPUT => 'token'])->assertOk();
-
- $event->assertDispatchedTimes(ReCaptchaResponseReceived::class, 3);
}
public function test_no_error_if_no_apk_same()
{
config(['captchavel.apk_package_name' => 'foo']);
- $event = Event::fake();
-
$mock = $this->mock(Captchavel::class);
- $mock->shouldReceive('useCredentials')->once()->with(2, 'checkbox')->andReturnSelf();
- $mock->shouldReceive('useCredentials')->once()->with(2, 'invisible')->andReturnSelf();
- $mock->shouldReceive('useCredentials')->once()->with(2, 'android')->andReturnSelf();
-
- $mock->shouldReceive('retrieve')
- ->times(3)
- ->with('token', '127.0.0.1')
- ->andReturn(new ReCaptchaResponse([
+ $response = new ReCaptchaResponse(
+ [
'success' => true,
- 'apk_package_name' => 'foo'
- ]));
+ 'score' => 0.5,
+ 'foo' => 'bar',
+ 'apk_package_name' => 'foo',
+ ]
+ );
+
+ $mock->shouldReceive('getChallenge')->once()->with('token', '127.0.0.1', 'checkbox')->andReturn($response);
+ $mock->shouldReceive('getChallenge')->once()->with('token', '127.0.0.1', 'invisible')->andReturn($response);
+ $mock->shouldReceive('getChallenge')->once()->with('token', '127.0.0.1', 'android')->andReturn($response);
$this->post('v2/checkbox', [Captchavel::INPUT => 'token'])->assertOk();
$this->post('v2/invisible', [Captchavel::INPUT => 'token'])->assertOk();
$this->post('v2/android', [Captchavel::INPUT => 'token'])->assertOk();
-
- $event->assertDispatchedTimes(ReCaptchaResponseReceived::class, 3);
}
public function test_exception_if_apk_not_equal()
{
config(['captchavel.apk_package_name' => 'bar']);
- $event = Event::fake();
-
$mock = $this->mock(Captchavel::class);
- $mock->shouldReceive('useCredentials')->twice()->with(2, 'checkbox')->andReturnSelf();
- $mock->shouldReceive('useCredentials')->twice()->with(2, 'invisible')->andReturnSelf();
- $mock->shouldReceive('useCredentials')->twice()->with(2, 'android')->andReturnSelf();
-
- $mock->shouldReceive('retrieve')
- ->times(6)
- ->with('token', '127.0.0.1')
- ->andReturn(new ReCaptchaResponse([
+ $response = new ReCaptchaResponse(
+ [
'success' => true,
- 'apk_package_name' => 'foo'
- ]));
+ 'score' => 0.5,
+ 'foo' => 'bar',
+ 'apk_package_name' => 'foo',
+ ]
+ );
+
+ $mock->shouldReceive('getChallenge')->twice()->with('token', '127.0.0.1', 'checkbox')->andReturn($response);
+ $mock->shouldReceive('getChallenge')->twice()->with('token', '127.0.0.1', 'invisible')->andReturn($response);
+ $mock->shouldReceive('getChallenge')->twice()->with('token', '127.0.0.1', 'android')->andReturn($response);
$this->post('v2/checkbox', [Captchavel::INPUT => 'token'])->assertRedirect('/');
$this->postJson('v2/checkbox', [Captchavel::INPUT => 'token'])->assertJsonValidationErrors('apk_package_name');
@@ -343,7 +310,5 @@ public function test_exception_if_apk_not_equal()
$this->postJson('v2/invisible', [Captchavel::INPUT => 'token'])->assertJsonValidationErrors('apk_package_name');
$this->post('v2/android', [Captchavel::INPUT => 'token'])->assertRedirect('/');
$this->postJson('v2/android', [Captchavel::INPUT => 'token'])->assertJsonValidationErrors('apk_package_name');
-
- $event->assertDispatchedTimes(ReCaptchaResponseReceived::class, 6);
}
}
diff --git a/tests/Http/Middleware/ScoreMiddlewareTest.php b/tests/Http/Middleware/ScoreMiddlewareTest.php
index 4021732..fa488fb 100644
--- a/tests/Http/Middleware/ScoreMiddlewareTest.php
+++ b/tests/Http/Middleware/ScoreMiddlewareTest.php
@@ -1,30 +1,30 @@
-afterApplicationCreated(function () {
- $this->createsRoutes();
- config(['captchavel.fake' => false]);
- });
+ $this->afterApplicationCreated(
+ function () {
+ $this->createsRoutes();
+ config(['captchavel.fake' => false]);
+ }
+ );
parent::setUp();
}
@@ -33,194 +33,151 @@ public function test_bypass_if_not_enabled()
{
config(['captchavel.enable' => false]);
- $event = Event::fake();
-
- $this->mock(Captchavel::class)->shouldNotReceive('useCredentials', 'retrieve');
+ $this->mock(Captchavel::class)->shouldNotReceive('getChallenge');
$this->post('v3/default')->assertOk();
-
- $event->assertNotDispatched(ReCaptchaResponseReceived::class);
}
public function test_validates_if_real()
{
$mock = $this->mock(Captchavel::class);
- $event = Event::fake();
-
- $mock->shouldReceive('useCredentials')
- ->with(3, null)
- ->andReturnSelf();
- $mock->shouldReceive('retrieve')
- ->with('token', '127.0.0.1')
- ->andReturn(new ReCaptchaResponse([
- 'success' => true,
- 'score' => 0.5,
- 'foo' => 'bar'
- ]));
-
- $this->post('v3/default', [
- Captchavel::INPUT => 'token'
- ])
+ $mock->shouldReceive('getChallenge')
+ ->with('token', '127.0.0.1', 'score')
+ ->andReturn(
+ new ReCaptchaResponse(
+ [
+ 'success' => true,
+ 'score' => 0.5,
+ 'foo' => 'bar',
+ ]
+ )
+ );
+
+ $this->post(
+ 'v3/default',
+ [
+ Captchavel::INPUT => 'token',
+ ]
+ )
->assertOk()
- ->assertExactJson([
- 'success' => true,
- 'score' => 0.5,
- 'foo' => 'bar'
- ]);
-
- $event->assertDispatched(ReCaptchaResponseReceived::class, function ($event) {
- return $event->response->foo === 'bar' && $event->request instanceof Request;
- });
+ ->assertExactJson(
+ [
+ 'success' => true,
+ 'score' => 0.5,
+ 'foo' => 'bar',
+ ]
+ );
}
public function test_fakes_human_response_automatically()
{
config(['captchavel.fake' => true]);
- $event = Event::fake();
+ Carbon::setTestNow(Carbon::now());
$this->post('v3/default')
->assertOk()
- ->assertExactJson([
- 'success' => true,
- 'score' => 1,
- 'action' => null,
- 'hostname' => null,
- 'apk_package_name' => null,
- ]);
-
- $event->assertDispatched(ReCaptchaResponseReceived::class, function ($event) {
- return $event->response->success === true && $event->request instanceof Request;
- });
+ ->assertExactJson(
+ [
+ 'success' => true,
+ 'score' => 1,
+ 'action' => null,
+ 'hostname' => null,
+ 'apk_package_name' => null,
+ 'challenge_ts' => Carbon::now()->toAtomString(),
+ ]
+ );
}
public function test_fakes_robot_response_if_input_is_robot_present()
{
config(['captchavel.fake' => true]);
- $event = Event::fake();
+ Carbon::setTestNow(Carbon::now());
$this->post('v3/default', ['is_robot' => 'on'])
->assertOk()
- ->assertExactJson([
- 'success' => true,
- 'score' => 0,
- 'action' => null,
- 'hostname' => null,
- 'apk_package_name' => null,
- ]);
-
- $event->assertDispatched(ReCaptchaResponseReceived::class, function ($event) {
- return $event->response->success === true && $event->request instanceof Request;
- });
+ ->assertExactJson(
+ [
+ 'success' => true,
+ 'score' => 0,
+ 'action' => null,
+ 'hostname' => null,
+ 'apk_package_name' => null,
+ 'challenge_ts' => Carbon::now()->toAtomString(),
+ ]
+ );
}
public function test_uses_custom_threshold()
{
$mock = $this->mock(Captchavel::class);
- $mock->shouldReceive('useCredentials')->andReturnSelf();
- $mock->shouldReceive('retrieve')->andReturn(new ReCaptchaResponse([
- 'success' => true,
- 'score' => 0.7,
- 'foo' => 'bar'
- ]));
+ $mock->shouldReceive('getChallenge')
+ ->with('token', '127.0.0.1', 'score')
+ ->andReturn(
+ (new ReCaptchaResponse(['success' => true, 'score' => 0.7, 'foo' => 'bar']))
+ ->setVersion(Captchavel::SCORE)
+ ->setAsResolved()
+ );
- $event = Event::fake();
-
- Route::post('test', function (ReCaptchaResponse $response) {
+ $this->app['router']->post('test', function (ReCaptchaResponse $response) {
return [$response->isHuman(), $response->isRobot(), $response->score];
- })->middleware('recaptcha.v3:0.7');
+ })->middleware('recaptcha.score:0.7');
- $this->post('test', [
- Captchavel::INPUT => 'token'
- ])
+ $this->post('test', [Captchavel::INPUT => 'token'])
->assertOk()
- ->assertExactJson([
- true, false, 0.7
- ]);
-
- $event->assertDispatched(ReCaptchaResponseReceived::class, function ($event) {
- return $event->response->success === true && $event->request instanceof Request;
- });
+ ->assertExactJson([true, false, 0.7]);
}
public function test_uses_custom_input()
{
$mock = $this->mock(Captchavel::class);
- $mock->shouldReceive('useCredentials')
- ->andReturnSelf();
- $mock->shouldReceive('retrieve')
- ->with('token', '127.0.0.1')
- ->andReturn(new ReCaptchaResponse([
- 'success' => true,
- 'score' => 0.7,
- 'foo' => 'bar'
- ]));
+ $mock->shouldReceive('getChallenge')
+ ->with('token', '127.0.0.1', 'score')
+ ->andReturn(
+ (new ReCaptchaResponse(['success' => true, 'score' => 0.7, 'foo' => 'bar']))
+ ->setVersion(Captchavel::SCORE)
+ ->setAsResolved()
+ );
- $event = Event::fake();
-
- Route::post('test', function (ReCaptchaResponse $response) {
+ $this->app['router']->post('test', function (ReCaptchaResponse $response) {
return $response;
- })->middleware('recaptcha.v3:null,null,foo');
+ })->middleware('recaptcha.score:null,null,foo');
- $this->post('test', [
- 'foo' => 'token'
- ])
+ $this->post('test', ['foo' => 'token'])
->assertOk()
- ->assertExactJson([
- 'success' => true,
- 'score' => 0.7,
- 'foo' => 'bar',
- ]);
-
- $event->assertDispatched(ReCaptchaResponseReceived::class, function ($event) {
- return $event->response->success === true && $event->request instanceof Request;
- });
+ ->assertExactJson(['success' => true, 'score' => 0.7, 'foo' => 'bar']);
}
public function test_exception_when_token_absent()
{
- $event = Event::fake();
-
- $this->post('v3/default', [
- 'foo' => 'bar'
- ])->assertRedirect('/');
-
- $this->postJson('v3/default', [
- 'foo' => 'bar'
- ])->assertJsonValidationErrors(Captchavel::INPUT);
+ $this->post('v3/default', ['foo' => 'bar'])
+ ->assertRedirect('/');
- $event->assertNotDispatched(ReCaptchaResponseReceived::class);
+ $this->postJson('v3/default', ['foo' => 'bar'])
+ ->assertJsonValidationErrors(Captchavel::INPUT);
}
public function test_exception_when_response_invalid()
{
- $event = Event::fake();
-
$mock = $this->mock(Captchavel::class);
- $mock->shouldReceive('useCredentials')
- ->andReturnSelf();
- $mock->shouldReceive('retrieve')
- ->with('token', '127.0.0.1')
- ->andReturn(new ReCaptchaResponse([
- 'success' => false,
- ]));
-
- $this->post('v3/default', [
- Captchavel::INPUT => 'token'
- ])->assertRedirect('/');
-
- $event->assertDispatched(ReCaptchaResponseReceived::class, function ($event) {
- return $event->response->success === false && $event->request instanceof Request;
- });
-
- $this->postJson('v3/default', [
- 'foo' => 'bar'
- ])->assertJsonValidationErrors(Captchavel::INPUT);
+ $mock->shouldReceive('getChallenge')
+ ->with('token', '127.0.0.1', 'score')
+ ->andReturn(
+ (new ReCaptchaResponse(['success' => false, 'score' => 0.7, 'foo' => 'bar']))
+ ->setVersion(Captchavel::SCORE)
+ ->setAsResolved()
+ );
+
+ $this->post('v3/default', [Captchavel::INPUT => 'token'])
+ ->assertRedirect('/');
+
+ $this->postJson('v3/default', ['foo' => 'bar'])
+ ->assertJsonValidationErrors(Captchavel::INPUT);
}
public function test_no_error_if_not_hostname_issued()
@@ -229,28 +186,19 @@ public function test_no_error_if_not_hostname_issued()
$mock = $this->mock(Captchavel::class);
- $mock->shouldReceive('useCredentials')
- ->andReturnSelf();
- $mock->shouldReceive('retrieve')
- ->with('token', '127.0.0.1')
- ->andReturn(new ReCaptchaResponse([
- 'success' => true,
- 'hostname' => 'foo',
- ]));
-
- $event = Event::fake();
-
- $this->post('v3/default', [
- Captchavel::INPUT => 'token'
- ])->assertOk();
+ $mock->shouldReceive('getChallenge')
+ ->with('token', '127.0.0.1', 'score')
+ ->andReturn(
+ (new ReCaptchaResponse(['success' => true, 'score' => 0.7, 'hostname' => 'foo']))
+ ->setVersion(Captchavel::SCORE)
+ ->setAsResolved()
+ );
- $event->assertDispatched(ReCaptchaResponseReceived::class, function ($event) {
- return $event->response->success === true && $event->request instanceof Request;
- });
+ $this->post('v3/default', [Captchavel::INPUT => 'token'])
+ ->assertOk();
- $this->postJson('v3/default', [
- Captchavel::INPUT => 'token'
- ])->assertOk();
+ $this->postJson('v3/default', [Captchavel::INPUT => 'token'])
+ ->assertOk();
}
public function test_no_error_if_hostname_same()
@@ -259,28 +207,19 @@ public function test_no_error_if_hostname_same()
$mock = $this->mock(Captchavel::class);
- $mock->shouldReceive('useCredentials')
- ->andReturnSelf();
- $mock->shouldReceive('retrieve')
- ->with('token', '127.0.0.1')
- ->andReturn(new ReCaptchaResponse([
- 'success' => true,
- 'hostname' => 'bar',
- ]));
-
- $event = Event::fake();
-
- $this->post('v3/default', [
- Captchavel::INPUT => 'token'
- ])->assertOk();
+ $mock->shouldReceive('getChallenge')
+ ->with('token', '127.0.0.1', 'score')
+ ->andReturn(
+ (new ReCaptchaResponse(['success' => true, 'score' => 0.7, 'hostname' => 'bar']))
+ ->setVersion(Captchavel::SCORE)
+ ->setAsResolved()
+ );
- $event->assertDispatched(ReCaptchaResponseReceived::class, function ($event) {
- return $event->response->success === true && $event->request instanceof Request;
- });
+ $this->post('v3/default', [Captchavel::INPUT => 'token'])
+ ->assertOk();
- $this->postJson('v3/default', [
- Captchavel::INPUT => 'token'
- ])->assertOk();
+ $this->postJson('v3/default', [Captchavel::INPUT => 'token'])
+ ->assertOk();
}
public function test_exception_if_hostname_not_equal()
@@ -289,28 +228,19 @@ public function test_exception_if_hostname_not_equal()
$mock = $this->mock(Captchavel::class);
- $mock->shouldReceive('useCredentials')
- ->andReturnSelf();
- $mock->shouldReceive('retrieve')
- ->with('token', '127.0.0.1')
- ->andReturn(new ReCaptchaResponse([
- 'success' => true,
- 'hostname' => 'foo',
- ]));
+ $mock->shouldReceive('getChallenge')
+ ->with('token', '127.0.0.1', 'score')
+ ->andReturn(
+ (new ReCaptchaResponse(['success' => true, 'score' => 0.7, 'hostname' => 'foo']))
+ ->setVersion(Captchavel::SCORE)
+ ->setAsResolved()
+ );
- $event = Event::fake();
+ $this->post('v3/default', [Captchavel::INPUT => 'token'])
+ ->assertRedirect('/');
- $this->post('v3/default', [
- Captchavel::INPUT => 'token'
- ])->assertRedirect('/');
-
- $event->assertDispatched(ReCaptchaResponseReceived::class, function ($event) {
- return $event->response->hostname === 'foo' && $event->request instanceof Request;
- });
-
- $this->postJson('v3/default', [
- Captchavel::INPUT => 'token'
- ])->assertJsonValidationErrors('hostname');
+ $this->postJson('v3/default', [Captchavel::INPUT => 'token'])
+ ->assertJsonValidationErrors('hostname');
}
public function test_no_error_if_no_apk_issued()
@@ -319,28 +249,19 @@ public function test_no_error_if_no_apk_issued()
$mock = $this->mock(Captchavel::class);
- $mock->shouldReceive('useCredentials')
- ->andReturnSelf();
- $mock->shouldReceive('retrieve')
- ->with('token', '127.0.0.1')
- ->andReturn(new ReCaptchaResponse([
- 'success' => true,
- 'apk_package_name' => 'foo',
- ]));
-
- $event = Event::fake();
+ $mock->shouldReceive('getChallenge')
+ ->with('token', '127.0.0.1', 'score')
+ ->andReturn(
+ (new ReCaptchaResponse(['success' => true, 'score' => 0.7, 'apk_package_name' => 'foo']))
+ ->setVersion(Captchavel::SCORE)
+ ->setAsResolved()
+ );
- $this->post('v3/default', [
- Captchavel::INPUT => 'token'
- ])->assertOk();
+ $this->post('v3/default', [Captchavel::INPUT => 'token'])
+ ->assertOk();
- $event->assertDispatched(ReCaptchaResponseReceived::class, function ($event) {
- return $event->response->apk_package_name === 'foo' && $event->request instanceof Request;
- });
-
- $this->postJson('v3/default', [
- Captchavel::INPUT => 'token'
- ])->assertOk();
+ $this->postJson('v3/default', [Captchavel::INPUT => 'token'])
+ ->assertOk();
}
public function test_no_error_if_apk_same()
@@ -349,28 +270,19 @@ public function test_no_error_if_apk_same()
$mock = $this->mock(Captchavel::class);
- $mock->shouldReceive('useCredentials')
- ->andReturnSelf();
- $mock->shouldReceive('retrieve')
- ->with('token', '127.0.0.1')
- ->andReturn(new ReCaptchaResponse([
- 'success' => true,
- 'apk_package_name' => 'foo',
- ]));
-
- $event = Event::fake();
+ $mock->shouldReceive('getChallenge')
+ ->with('token', '127.0.0.1', 'score')
+ ->andReturn(
+ (new ReCaptchaResponse(['success' => true, 'score' => 0.7, 'apk_package_name' => 'foo']))
+ ->setVersion(Captchavel::SCORE)
+ ->setAsResolved()
+ );
- $this->post('v3/default', [
- Captchavel::INPUT => 'token'
- ])->assertOk();
+ $this->post('v3/default', [Captchavel::INPUT => 'token'])
+ ->assertOk();
- $event->assertDispatched(ReCaptchaResponseReceived::class, function ($event) {
- return $event->response->apk_package_name === 'foo' && $event->request instanceof Request;
- });
-
- $this->postJson('v3/default', [
- Captchavel::INPUT => 'token'
- ])->assertOk();
+ $this->postJson('v3/default', [Captchavel::INPUT => 'token'])
+ ->assertOk();
}
public function test_exception_if_apk_not_equal()
@@ -379,118 +291,85 @@ public function test_exception_if_apk_not_equal()
$mock = $this->mock(Captchavel::class);
- $mock->shouldReceive('useCredentials')
- ->andReturnSelf();
- $mock->shouldReceive('retrieve')
- ->with('token', '127.0.0.1')
- ->andReturn(new ReCaptchaResponse([
- 'success' => true,
- 'apk_package_name' => null,
- ]));
-
- $event = Event::fake();
-
- $this->post('v3/default', [
- Captchavel::INPUT => 'token'
- ])->assertRedirect('/');
+ $mock->shouldReceive('getChallenge')
+ ->with('token', '127.0.0.1', 'score')
+ ->andReturn(
+ (new ReCaptchaResponse(['success' => true, 'score' => 0.7, 'apk_package_name' => null]))
+ ->setVersion(Captchavel::SCORE)
+ ->setAsResolved()
+ );
- $event->assertDispatched(ReCaptchaResponseReceived::class, function ($event) {
- return $event->response->apk_package_name === null && $event->request instanceof Request;
- });
+ $this->post('v3/default', [Captchavel::INPUT => 'token'])
+ ->assertRedirect('/');
- $this->postJson('v3/default', [
- Captchavel::INPUT => 'token'
- ])->assertJsonValidationErrors('apk_package_name');
+ $this->postJson('v3/default', [Captchavel::INPUT => 'token'])
+ ->assertJsonValidationErrors('apk_package_name');
}
public function test_no_error_if_no_action()
{
$mock = $this->mock(Captchavel::class);
- $mock->shouldReceive('useCredentials')
- ->andReturnSelf();
- $mock->shouldReceive('retrieve')
- ->with('token', '127.0.0.1')
- ->andReturn(new ReCaptchaResponse([
- 'success' => true,
- 'apk_package_name' => null,
- 'action' => 'foo',
- ]));
+ $mock->shouldReceive('getChallenge')
+ ->with('token', '127.0.0.1', 'score')
+ ->andReturn(
+ (new ReCaptchaResponse(['success' => true, 'action' => 'foo', 'apk_package_name' => null]))
+ ->setVersion(Captchavel::SCORE)
+ ->setAsResolved()
+ );
- $event = Event::fake();
-
- Route::post('test', function (ReCaptchaResponse $response) {
+ $this->app['router']->post('test', function (ReCaptchaResponse $response) {
return $response;
- })->middleware('recaptcha.v3:null,null');
-
- $this->post('test', [
- Captchavel::INPUT => 'token'
- ])->assertOk();
+ })->middleware('recaptcha.score:null,null');
- $event->assertDispatched(ReCaptchaResponseReceived::class, function ($event) {
- return $event->response->action === 'foo' && $event->request instanceof Request;
- });
+ $this->post('test', [Captchavel::INPUT => 'token'])->assertOk();
}
public function test_no_error_if_action_same()
{
$mock = $this->mock(Captchavel::class);
- $mock->shouldReceive('useCredentials')
- ->andReturnSelf();
- $mock->shouldReceive('retrieve')
- ->with('token', '127.0.0.1')
- ->andReturn(new ReCaptchaResponse([
- 'success' => true,
- 'apk_package_name' => null,
- 'action' => 'foo',
- ]));
+ $mock->shouldReceive('getChallenge')
+ ->with('token', '127.0.0.1', 'score')
+ ->andReturn(
+ (new ReCaptchaResponse(['success' => true, 'action' => 'foo', 'apk_package_name' => null]))
+ ->setVersion(Captchavel::SCORE)
+ ->setAsResolved()
+ );
- $event = Event::fake();
-
- Route::post('test', function (ReCaptchaResponse $response) {
+ $this->app['router']->post('test', function (ReCaptchaResponse $response) {
return $response;
- })->middleware('recaptcha.v3:null,foo');
-
- $this->post('test', [
- Captchavel::INPUT => 'token'
- ])->assertOk();
+ })->middleware('recaptcha.score:null,foo');
- $event->assertDispatched(ReCaptchaResponseReceived::class, function ($event) {
- return $event->response->action === 'foo' && $event->request instanceof Request;
- });
+ $this->post('test', [Captchavel::INPUT => 'token'])->assertOk();
}
public function test_exception_if_action_not_equal()
{
$mock = $this->mock(Captchavel::class);
- $mock->shouldReceive('useCredentials')
- ->andReturnSelf();
- $mock->shouldReceive('retrieve')
- ->with('token', '127.0.0.1')
- ->andReturn(new ReCaptchaResponse([
- 'success' => true,
- 'apk_package_name' => null,
- 'action' => 'foo',
- ]));
-
- Route::post('test', function (ReCaptchaResponse $response) {
- return $response;
- })->middleware('recaptcha.v3:null,bar');
-
- $this->post('test', [
- Captchavel::INPUT => 'token'
- ])->assertRedirect('/');
-
- $this->postJson('test', [
- Captchavel::INPUT => 'token'
- ])->assertJsonValidationErrors('action');
+ $mock->shouldReceive('getChallenge')
+ ->with('token', '127.0.0.1', 'score')
+ ->andReturn(
+ (new ReCaptchaResponse(['success' => true, 'action' => 'foo', 'apk_package_name' => null]))
+ ->setVersion(Captchavel::SCORE)
+ ->setAsResolved()
+ );
+
+ $this->app['router']->post(
+ 'test',
+ function (ReCaptchaResponse $response) {
+ return $response;
+ }
+ )->middleware('recaptcha.score:null,bar');
+
+ $this->post('test', [Captchavel::INPUT => 'token'])->assertRedirect('/');
+ $this->postJson('test', [Captchavel::INPUT => 'token'])->assertJsonValidationErrors('action');
}
public function test_checks_for_human_score()
{
- config(['captchavel.credentials.v3.secret' => 'secret']);
+ config(['captchavel.credentials.score.secret' => 'secret']);
config(['captchavel.fake' => false]);
$mock = $this->mock(Factory::class);
@@ -498,32 +377,55 @@ public function test_checks_for_human_score()
$mock->shouldReceive('asForm')->withNoArgs()->times(4)->andReturnSelf();
$mock->shouldReceive('withOptions')->with(['version' => 2.0])->times(4)->andReturnSelf();
$mock->shouldReceive('post')
- ->with(Captchavel::RECAPTCHA_ENDPOINT, [
- 'secret' => 'secret',
- 'response' => 'token',
- 'remoteip' => '127.0.0.1',
- ])
+ ->with(
+ Captchavel::RECAPTCHA_ENDPOINT,
+ [
+ 'secret' => 'secret',
+ 'response' => 'token',
+ 'remoteip' => '127.0.0.1',
+ ]
+ )
->times(4)
- ->andReturn(new Response(new GuzzleResponse(200, ['Content-type' => 'application/json'], json_encode([
- 'success' => true,
- 'score' => 0.5,
- ]))));
-
- Route::post('human_human', function (Request $request) {
- return $request->isHuman() ? 'true' : 'false';
- })->middleware('recaptcha.v3:0.7');
-
- Route::post('human_robot', function (Request $request) {
- return $request->isRobot() ? 'true' : 'false';
- })->middleware('recaptcha.v3:0.7');
-
- Route::post('robot_human', function (Request $request) {
- return $request->isHuman() ? 'true' : 'false';
- })->middleware('recaptcha.v3:0.3');
-
- Route::post('robot_robot', function (Request $request) {
- return $request->isRobot() ? 'true' : 'false';
- })->middleware('recaptcha.v3:0.3');
+ ->andReturn(
+ new Response(
+ new GuzzleResponse(
+ 200, ['Content-type' => 'application/json'], json_encode(
+ [
+ 'success' => true,
+ 'score' => 0.5,
+ ]
+ )
+ )
+ )
+ );
+
+ $this->app['router']->post(
+ 'human_human',
+ function (Request $request) {
+ return $request->isHuman() ? 'true' : 'false';
+ }
+ )->middleware('recaptcha.score:0.7');
+
+ $this->app['router']->post(
+ 'human_robot',
+ function (Request $request) {
+ return $request->isRobot() ? 'true' : 'false';
+ }
+ )->middleware('recaptcha.score:0.7');
+
+ $this->app['router']->post(
+ 'robot_human',
+ function (Request $request) {
+ return $request->isHuman() ? 'true' : 'false';
+ }
+ )->middleware('recaptcha.score:0.3');
+
+ $this->app['router']->post(
+ 'robot_robot',
+ function (Request $request) {
+ return $request->isRobot() ? 'true' : 'false';
+ }
+ )->middleware('recaptcha.score:0.3');
$this->post('human_human', [Captchavel::INPUT => 'token'])->assertSee('false');
$this->post('human_robot', [Captchavel::INPUT => 'token'])->assertSee('true');
diff --git a/tests/Http/Middleware/UsesRoutesWithMiddleware.php b/tests/Http/Middleware/UsesRoutesWithMiddleware.php
index befffe3..630385c 100644
--- a/tests/Http/Middleware/UsesRoutesWithMiddleware.php
+++ b/tests/Http/Middleware/UsesRoutesWithMiddleware.php
@@ -2,7 +2,6 @@
namespace Tests\Http\Middleware;
-use Illuminate\Support\Facades\Route;
use DarkGhostHunter\Captchavel\Http\ReCaptchaResponse;
trait UsesRoutesWithMiddleware
@@ -11,48 +10,48 @@ protected function createsRoutes()
{
config(['captchavel.enable' => true]);
- Route::post('v3/default', function (ReCaptchaResponse $response) {
+ $this->app['router']->post('v3/default', function (ReCaptchaResponse $response) {
return $response;
- })->middleware('recaptcha.v3');
+ })->middleware('recaptcha.score');
- Route::post('v3/threshold_1', function (ReCaptchaResponse $response) {
+ $this->app['router']->post('v3/threshold_1', function (ReCaptchaResponse $response) {
return $response;
- })->middleware('recaptcha.v3:1.0');
+ })->middleware('recaptcha.score:1.0');
- Route::post('v3/threshold_0', function (ReCaptchaResponse $response) {
+ $this->app['router']->post('v3/threshold_0', function (ReCaptchaResponse $response) {
return $response;
- })->middleware('recaptcha.v3:0');
+ })->middleware('recaptcha.score:0');
- Route::post('v3/action_foo', function (ReCaptchaResponse $response) {
+ $this->app['router']->post('v3/action_foo', function (ReCaptchaResponse $response) {
return $response;
- })->middleware('recaptcha.v3:null,foo');
+ })->middleware('recaptcha.score:null,foo');
- Route::post('v3/input_bar', function (ReCaptchaResponse $response) {
+ $this->app['router']->post('v3/input_bar', function (ReCaptchaResponse $response) {
return $response;
- })->middleware('recaptcha.v3:null,null,bar');
+ })->middleware('recaptcha.score:null,null,bar');
- Route::post('v2/checkbox', function (ReCaptchaResponse $response) {
+ $this->app['router']->post('v2/checkbox', function (ReCaptchaResponse $response) {
return $response;
- })->middleware('recaptcha.v2:checkbox');
+ })->middleware('recaptcha:checkbox');
- Route::post('v2/checkbox/input_bar', function (ReCaptchaResponse $response) {
+ $this->app['router']->post('v2/checkbox/input_bar', function (ReCaptchaResponse $response) {
return $response;
- })->middleware('recaptcha.v2:checkbox,bar');
+ })->middleware('recaptcha:checkbox,bar');
- Route::post('v2/invisible', function (ReCaptchaResponse $response) {
+ $this->app['router']->post('v2/invisible', function (ReCaptchaResponse $response) {
return $response;
- })->middleware('recaptcha.v2:invisible');
+ })->middleware('recaptcha:invisible');
- Route::post('v2/invisible/input_bar', function (ReCaptchaResponse $response) {
+ $this->app['router']->post('v2/invisible/input_bar', function (ReCaptchaResponse $response) {
return $response;
- })->middleware('recaptcha.v2:invisible,bar');
+ })->middleware('recaptcha:invisible,bar');
- Route::post('v2/android', function (ReCaptchaResponse $response) {
+ $this->app['router']->post('v2/android', function (ReCaptchaResponse $response) {
return $response;
- })->middleware('recaptcha.v2:android');
+ })->middleware('recaptcha:android');
- Route::post('v2/android/input_bar', function (ReCaptchaResponse $response) {
+ $this->app['router']->post('v2/android/input_bar', function (ReCaptchaResponse $response) {
return $response;
- })->middleware('recaptcha.v2:android,bar');
+ })->middleware('recaptcha:android,bar');
}
}
diff --git a/tests/Http/ReCaptchaResponseTest.php b/tests/Http/ReCaptchaResponseTest.php
index 5b99c23..d6ef251 100644
--- a/tests/Http/ReCaptchaResponseTest.php
+++ b/tests/Http/ReCaptchaResponseTest.php
@@ -2,10 +2,11 @@
namespace Tests\Http;
-use LogicException;
-use Tests\RegistersPackage;
-use Orchestra\Testbench\TestCase;
+use DarkGhostHunter\Captchavel\Captchavel;
use DarkGhostHunter\Captchavel\Http\ReCaptchaResponse;
+use Orchestra\Testbench\TestCase;
+use RuntimeException;
+use Tests\RegistersPackage;
class ReCaptchaResponseTest extends TestCase
{
@@ -13,11 +14,49 @@ class ReCaptchaResponseTest extends TestCase
public function test_exception_when_checking_non_score_response()
{
- $this->expectException(LogicException::class);
- $this->expectExceptionMessage('This is not a reCAPTCHA v3 response, or the score is absent.');
+ $this->expectException(RuntimeException::class);
+ $this->expectExceptionMessage('This is not a reCAPTCHA v3 response');
(new ReCaptchaResponse([
'success' => true,
- ]))->isHuman();
+ ]))->setAsResolved()->isHuman();
+ }
+
+ public function test_exception_when_score_response_has_no_score()
+ {
+ $this->expectException(RuntimeException::class);
+ $this->expectExceptionMessage('This is reCAPTCHA v3 response has no score');
+
+ (new ReCaptchaResponse([
+ 'success' => true,
+ ]))->setVersion(Captchavel::SCORE)->setAsResolved()->isHuman();
+ }
+
+ public function test_is_checkbox_response()
+ {
+ static::assertTrue(
+ (new ReCaptchaResponse())->setVersion(Captchavel::CHECKBOX)->isCheckbox()
+ );
+ }
+
+ public function test_is_invisible_response()
+ {
+ static::assertTrue(
+ (new ReCaptchaResponse())->setVersion(Captchavel::INVISIBLE)->isInvisible()
+ );
+ }
+
+ public function test_is_android_response()
+ {
+ static::assertTrue(
+ (new ReCaptchaResponse())->setVersion(Captchavel::ANDROID)->isAndroid()
+ );
+ }
+
+ public function test_is_score_response()
+ {
+ static::assertTrue(
+ (new ReCaptchaResponse())->setVersion(Captchavel::SCORE)->isScore()
+ );
}
}