Skip to content
Open
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,20 @@ with additional error codes based on the client's checks. When adding a new
[`RequestMethod`](./src/ReCaptcha/RequestMethod.php) ensure that it returns the
`ReCaptcha::E_CONNECTION_FAILED` and `ReCaptcha::E_BAD_RESPONSE` where
appropriate.

## Public API compatibility

The 1.x line treats the following classes and interfaces as public API:
`ReCaptcha`, `RequestMethod`, `Response`, `RequestParameters`,
`RequestMethod\Post`, `RequestMethod\CurlPost`, and
`RequestMethod\SocketPost`, plus the request wrapper classes
`RequestMethod\Curl` and `RequestMethod\Socket`.

Changes that narrow those APIs, such as adding native scalar parameter types,
adding native return types to existing public methods, making public non-final
classes `readonly` or `final`, removing public classes, or removing existing
constructor argument forms, should be reserved for a major release.

The `RequestMethod::submit()` interface intentionally keeps its 1.x-compatible
native signature. Implementations are still expected to return the body of the
reCAPTCHA response as a string.
7 changes: 6 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ and v3.
- reCAPTCHA: https://cloud.google.com/security/products/recaptcha
- This repo: https://github.com/google/recaptcha
- Hosted demo: https://recaptcha-demo.appspot.com/
- Version: 1.5.0
- Version: 1.5.1
- License: BSD, see [LICENSE](LICENSE)

> [!IMPORTANT]
Expand Down Expand Up @@ -169,6 +169,11 @@ $recaptcha = new \ReCaptcha\ReCaptcha($secret, new \ReCaptcha\RequestMethod\Sock

For more details on usage and structure, see [ARCHITECTURE](ARCHITECTURE.md).

The 1.x line preserves compatibility for the public request and response APIs.
See [Public API compatibility](ARCHITECTURE.md#public-api-compatibility) for
details on which API changes require a major release.


### Examples

You can see examples of each reCAPTCHA type in [examples/](examples/). You can
Expand Down
4 changes: 2 additions & 2 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,14 @@
},
"extra": {
"branch-alias": {
"dev-main": "1.4.x-dev"
"dev-main": "1.5.x-dev"
}
},
"scripts": {
"lint": "vendor/bin/php-cs-fixer -vvv check --using-cache=no",
"lint-fix": "vendor/bin/php-cs-fixer -vvv fix --using-cache=no",
"phpstan": "vendor/bin/phpstan",
"test": "XDEBUG_MODE=coverage vendor/bin/phpunit",
"test": "@php -d xdebug.mode=coverage vendor/bin/phpunit",
"serve-examples": "@php -S localhost:8080 -t examples"
},
"config": {
Expand Down
90 changes: 69 additions & 21 deletions src/ReCaptcha/ReCaptcha.php
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ class ReCaptcha
*
* @var string
*/
public const VERSION = 'php_1.5.0';
public const VERSION = 'php_1.5.1';

/**
* URL for reCAPTCHA siteverify API.
Expand Down Expand Up @@ -154,13 +154,17 @@ class ReCaptcha
/**
* Create a configured instance to use the reCAPTCHA service.
*
* @param string $secret the shared key between your site and reCAPTCHA
* @param mixed $secret the shared key between your site and reCAPTCHA
* @param RequestMethod $requestMethod method used to send the request. Defaults to POST.
*
* @throws \RuntimeException if $secret is invalid
*/
public function __construct(string $secret, ?RequestMethod $requestMethod = null)
public function __construct($secret, ?RequestMethod $requestMethod = null)
{
if (!is_string($secret)) {
throw new \RuntimeException('The provided secret must be a string');
}

if ('' === $secret) {
throw new \RuntimeException('No secret provided');
}
Expand All @@ -180,20 +184,28 @@ public function __construct(string $secret, ?RequestMethod $requestMethod = null
* Calls the reCAPTCHA siteverify API to verify whether the user passes
* CAPTCHA test and additionally runs any specified additional checks.
*
* @param string $response the user response token provided by reCAPTCHA, verifying the user on your site
* @param null|string $remoteIp the end user's IP address
* @param mixed $response the user response token provided by reCAPTCHA, verifying the user on your site
Comment thread
SNO7E-G marked this conversation as resolved.
Outdated
* @param mixed $remoteIp the end user's IP address
*
* @return Response response from the service
*/
public function verify(string $response, ?string $remoteIp = null): Response
public function verify($response, $remoteIp = null)
{
$response = self::stringValue($response);

// Discard empty solution submissions
if ('' === $response) {
return new Response(false, [self::E_MISSING_INPUT_RESPONSE]);
}

$remoteIp = self::nullableStringValue($remoteIp);
$params = new RequestParameters($this->secret, $response, $remoteIp, self::VERSION);
$rawResponse = $this->requestMethod->submit($params);

if (!is_string($rawResponse)) {
return new Response(false, [self::E_BAD_RESPONSE]);
}

$initialResponse = Response::fromJson($rawResponse);
$validationErrors = [];

Expand Down Expand Up @@ -240,27 +252,27 @@ public function verify(string $response, ?string $remoteIp = null): Response
* Provide a hostname to match against in verify()
* This should be without a protocol or trailing slash, e.g. www.google.com.
*
* @param string $hostname Expected hostname
* @param mixed $hostname Expected hostname
*
* @return ReCaptcha Current instance for fluent interface
*/
public function setExpectedHostname(string $hostname): self
public function setExpectedHostname($hostname)
{
$this->hostname = $hostname;
$this->hostname = self::stringValue($hostname);

return $this;
}

/**
* Provide an APK package name to match against in verify().
*
* @param string $apkPackageName Expected APK package name
* @param mixed $apkPackageName Expected APK package name
*
* @return ReCaptcha Current instance for fluent interface
*/
public function setExpectedApkPackageName(string $apkPackageName): self
public function setExpectedApkPackageName($apkPackageName)
{
$this->apkPackageName = $apkPackageName;
$this->apkPackageName = self::stringValue($apkPackageName);

return $this;
}
Expand All @@ -269,13 +281,13 @@ public function setExpectedApkPackageName(string $apkPackageName): self
* Provide an action to match against in verify()
* This should be set per page.
*
* @param string $action Expected action
* @param mixed $action Expected action
*
* @return ReCaptcha Current instance for fluent interface
*/
public function setExpectedAction(string $action): self
public function setExpectedAction($action)
{
$this->action = $action;
$this->action = self::stringValue($action);

return $this;
}
Expand All @@ -284,28 +296,64 @@ public function setExpectedAction(string $action): self
* Provide a threshold to meet or exceed in verify()
* Threshold should be a float between 0 and 1 which will be tested as response >= threshold.
*
* @param float $threshold Expected threshold
* @param mixed $threshold Expected threshold
*
* @return ReCaptcha Current instance for fluent interface
*/
public function setScoreThreshold(float $threshold): self
public function setScoreThreshold($threshold)
{
$this->threshold = $threshold;
$this->threshold = self::floatValue($threshold);

return $this;
}

/**
* Provide a timeout in seconds to test against the challenge timestamp in verify().
*
* @param int $timeoutSeconds Maximum time (seconds) elapsed since the challenge timestamp
* @param mixed $timeoutSeconds Maximum time (seconds) elapsed since the challenge timestamp
*
* @return ReCaptcha Current instance for fluent interface
*/
public function setChallengeTimeout(int $timeoutSeconds): self
public function setChallengeTimeout($timeoutSeconds)
{
$this->timeoutSeconds = $timeoutSeconds;
$this->timeoutSeconds = self::intValue($timeoutSeconds);

return $this;
}

private static function nullableStringValue(mixed $value): ?string
{
if (is_null($value)) {
return null;
}

return self::stringValue($value);
}

private static function stringValue(mixed $value): string
{
if (is_scalar($value) || $value instanceof \Stringable) {
return (string) $value;
}

return '';
Comment thread
SNO7E-G marked this conversation as resolved.
}

private static function floatValue(mixed $value): float
{
if (is_null($value) || is_scalar($value)) {
return floatval($value);
}

return 0.0;
}

private static function intValue(mixed $value): int
{
if (is_null($value) || is_scalar($value)) {
return intval($value);
}

return 0;
}
}
4 changes: 2 additions & 2 deletions src/ReCaptcha/RequestMethod.php
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ interface RequestMethod
*
* @param RequestParameters $params Request parameters
*
* @return string Body of the reCAPTCHA response
* @return mixed Body of the reCAPTCHA response
Comment thread
SNO7E-G marked this conversation as resolved.
Outdated
*/
public function submit(RequestParameters $params): string;
public function submit(RequestParameters $params);
}
90 changes: 90 additions & 0 deletions src/ReCaptcha/RequestMethod/Curl.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
<?php

declare(strict_types=1);

/**
* This is a PHP library that handles calling reCAPTCHA.
*
* BSD 3-Clause License
*
* @copyright (c) 2019, Google Inc.
*
* @see https://www.google.com/recaptcha
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* 3. Neither the name of the copyright holder nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

namespace ReCaptcha\RequestMethod;

/**
* Convenience wrapper around the cURL functions to allow mocking.
*/
class Curl
{
/**
* @param null|string $url
*
* @return mixed
*/
public function init($url = null)
{
return curl_init($url);
}

/**
* @param mixed $ch
* @param array<int, mixed> $options
*
* @return bool
*/
public function setoptArray($ch, array $options)
{
// @phpstan-ignore argument.type
return curl_setopt_array($ch, $options);
}

/**
* @param mixed $ch
*
* @return mixed
*/
public function exec($ch)
{
// @phpstan-ignore argument.type
return curl_exec($ch);
}

/**
* @param mixed $ch
*
* @phpstan-return void
*/
public function close($ch)
{
// @phpstan-ignore argument.type
curl_close($ch);
Comment thread
SNO7E-G marked this conversation as resolved.
Outdated
}
}
Loading
Loading