Skip to content

Commit

Permalink
Add SymfonyHttpClient new ReCaptcha request method
Browse files Browse the repository at this point in the history
  • Loading branch information
norkunas authored and karser committed May 18, 2023
1 parent 80a5f78 commit 37e9d92
Show file tree
Hide file tree
Showing 9 changed files with 185 additions and 1 deletion.
7 changes: 7 additions & 0 deletions DependencyInjection/KarserRecaptcha3Extension.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
use Symfony\Component\DependencyInjection\Extension\PrependExtensionInterface;
use Symfony\Component\DependencyInjection\Loader;
use Symfony\Component\HttpKernel\DependencyInjection\ConfigurableExtension;
use Symfony\Contracts\HttpClient\HttpClientInterface;

class KarserRecaptcha3Extension extends ConfigurableExtension implements PrependExtensionInterface
{
Expand All @@ -17,6 +18,12 @@ public function loadInternal(array $configs, ContainerBuilder $container): void
foreach ($configs as $key => $value) {
$container->setParameter('karser_recaptcha3.'.$key, $value);
}

if (interface_exists(HttpClientInterface::class)) {
$container->setAlias('karser_recaptcha3.google.request_method', 'karser_recaptcha3.request_method.symfony_http_client');
} else {
$container->removeDefinition('karser_recaptcha3.request_method.symfony_http_client');
}
}

public function prepend(ContainerBuilder $container): void
Expand Down
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -339,6 +339,11 @@ services:
$requestStack: '@request_stack'
```
### Symfony HttpClient integration
If you have a dependency on `symfony/http-client` in your application then it will be automatically wired
to use via `RequestMethod/SymfonyHttpClient`.

Troubleshooting checklist
-------------------------

Expand Down
49 changes: 49 additions & 0 deletions RequestMethod/SymfonyHttpClient.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
<?php

declare(strict_types=1);

namespace Karser\Recaptcha3Bundle\RequestMethod;

use ReCaptcha\ReCaptcha;
use ReCaptcha\RequestMethod;
use ReCaptcha\RequestParameters;
use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface;
use Symfony\Contracts\HttpClient\HttpClientInterface;

final class SymfonyHttpClient implements RequestMethod
{
/**
* @var HttpClientInterface
*/
private $httpClient;

/**
* @var string
*/
private $siteVerifyUrl;

public function __construct(HttpClientInterface $httpClient, ?string $siteVerifyUrl = null)
{
$this->httpClient = $httpClient;
$this->siteVerifyUrl = $siteVerifyUrl ?? ReCaptcha::SITE_VERIFY_URL;
}

public function submit(RequestParameters $params): string
{
$response = $this->httpClient->request('POST', $this->siteVerifyUrl, [
'body' => $params->toArray(),
]);

try {
$statusCode = $response->getStatusCode();
} catch (TransportExceptionInterface $e) {
return '{"success": false, "error-codes": ["'.ReCaptcha::E_CONNECTION_FAILED.'"]}';
}

if ($statusCode !== 200) {
return '{"success": false, "error-codes": ["'.ReCaptcha::E_BAD_RESPONSE.'"]}';
}

return $response->getContent(false);
}
}
7 changes: 7 additions & 0 deletions Resources/config/services.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
use Karser\Recaptcha3Bundle\Form\Recaptcha3Type;
use Karser\Recaptcha3Bundle\Services\HostProvider;
use Karser\Recaptcha3Bundle\Services\IpResolver;
use Karser\Recaptcha3Bundle\RequestMethod\SymfonyHttpClient;
use Karser\Recaptcha3Bundle\Validator\Constraints\Recaptcha3Validator;
use ReCaptcha\ReCaptcha;
use ReCaptcha\RequestMethod\Curl;
Expand Down Expand Up @@ -57,4 +58,10 @@
]);

$services->set('karser_recaptcha3.google.request_method.curl', Curl::class);

$services->set('karser_recaptcha3.request_method.symfony_http_client', SymfonyHttpClient::class)
->args([
(new ReferenceConfigurator('http_client'))->ignoreOnInvalid(),
new Expression("service('karser_recaptcha3.host_provider').getVerifyUrl()")
]);
};
16 changes: 16 additions & 0 deletions Tests/FunctionalTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormFactoryInterface;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\HttpKernel\Kernel;
use Symfony\Component\Validator\Constraints\NotBlank;

class FunctionalTest extends TestCase
Expand Down Expand Up @@ -213,6 +214,21 @@ public function testFormJavascriptAltHostIsPreserved_ifSet()
self::assertStringContainsString('<script type="text/javascript" src="https://www.recaptcha.net/recaptcha/api.js?render=key&hl=en&onload=recaptchaCallback_form_captcha" async defer nonce=""></script>', $view);
}

public function testUsesSymfonyHttpClient()
{
if (Kernel::VERSION_ID < 50200) {
self::markTestSkipped('skip');
}

$this->bootKernel('http_client.yml');

$form = $this->createContactForm($this->formFactory);
$form->submit(['name' => 'John', 'captcha' => 'token']);

self::assertTrue($form->isSubmitted());
self::assertTrue($form->isValid());
}

private function assertFormHasCaptchaError(FormInterface $form, string $expectedMessage)
{
self::assertTrue($form->isSubmitted());
Expand Down
62 changes: 62 additions & 0 deletions Tests/RequestMethod/SymfonyHttpClientTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
<?php

declare(strict_types=1);

namespace RequestMethod;

use Karser\Recaptcha3Bundle\RequestMethod\SymfonyHttpClient;
use PHPUnit\Framework\TestCase;
use ReCaptcha\RequestParameters;
use Symfony\Component\HttpClient\MockHttpClient;
use Symfony\Component\HttpClient\Response\MockResponse;

final class SymfonyHttpClientTest extends TestCase
{
public function testSubmit(): void
{
$httpClient = new MockHttpClient(function (string $method, string $url, array $options) {
self::assertSame('POST', $method);
self::assertSame('https://www.google.com/recaptcha/api/siteverify', $url);
self::assertSame('secret=secret&response=response', $options['body']);

return new MockResponse('RESPONSEBODY');
});

$method = new SymfonyHttpClient($httpClient);
$response = $method->submit(new RequestParameters('secret', 'response'));

self::assertSame('RESPONSEBODY', $response);
}

public function testOverrideSiteVerifyUrl()
{
$httpClient = new MockHttpClient(function (string $method, string $url, array $options) {
self::assertSame('POST', $method);
self::assertSame('http://override/', $url);
self::assertSame('secret=secret&response=response', $options['body']);

return new MockResponse('RESPONSEBODY');
});

$method = new SymfonyHttpClient($httpClient, 'http://override/');
$response = $method->submit(new RequestParameters('secret', 'response'));

self::assertSame('RESPONSEBODY', $response);
}

public function testResponseError()
{
$httpClient = new MockHttpClient(function (string $method, string $url, array $options) {
self::assertSame('POST', $method);
self::assertSame('https://www.google.com/recaptcha/api/siteverify', $url);
self::assertSame('secret=secret&response=response', $options['body']);

return new MockResponse('fail', ['http_code' => 400]);
});

$method = new SymfonyHttpClient($httpClient);
$response = $method->submit(new RequestParameters('secret', 'response'));

self::assertSame('{"success": false, "error-codes": ["bad-response"]}', $response);
}
}
14 changes: 14 additions & 0 deletions Tests/fixtures/MockHttpClientCallback.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?php declare(strict_types=1);

namespace Karser\Recaptcha3Bundle\Tests\fixtures;

use Symfony\Component\HttpClient\Response\MockResponse;
use Symfony\Contracts\HttpClient\ResponseInterface;

class MockHttpClientCallback
{
public function __invoke(string $method, string $url, array $options = []): ResponseInterface
{
return new MockResponse('{"success": true, "score": 1}');
}
}
23 changes: 23 additions & 0 deletions Tests/fixtures/config/http_client.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
framework:
secret: ThisIsNotReallyASecretSoPleaseChangeIt
test: true
form: ~
http_client:
mock_response_factory: Karser\Recaptcha3Bundle\Tests\fixtures\MockHttpClientCallback

services:
form.factory.public:
alias: form.factory
public: true
twig.public:
alias: twig
public: true
karser_recaptcha3.google.recaptcha.public:
alias: karser_recaptcha3.google.recaptcha
public: true
Karser\Recaptcha3Bundle\Tests\fixtures\MockHttpClientCallback: ~

karser_recaptcha3:
site_key: 'key'
secret_key: 'secret'
enabled: true
3 changes: 2 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,8 @@
"twig/twig": "^2.9|^3.0"
},
"require-dev": {
"phpunit/phpunit": "^7|^8|^9"
"phpunit/phpunit": "^7|^8|^9",
"symfony/http-client": "^4.3|^5.0|^6.0"
},
"autoload": {
"psr-4": {
Expand Down

0 comments on commit 37e9d92

Please sign in to comment.