forked from GithubBackups/vichan
captcha-queries.php: refactor remote captcha queries to use DI and abstract the implementation better
This commit is contained in:
parent
e825e7aac5
commit
933594194c
@ -3,6 +3,9 @@ namespace Vichan;
|
|||||||
|
|
||||||
use RuntimeException;
|
use RuntimeException;
|
||||||
use Vichan\Driver\{HttpDriver, HttpDrivers, Log, LogDrivers};
|
use Vichan\Driver\{HttpDriver, HttpDrivers, Log, LogDrivers};
|
||||||
|
use Vichan\Service\HCaptchaQuery;
|
||||||
|
use Vichan\Service\ReCaptchaQuery;
|
||||||
|
use Vichan\Service\RemoteCaptchaQuery;
|
||||||
|
|
||||||
defined('TINYBOARD') or exit;
|
defined('TINYBOARD') or exit;
|
||||||
|
|
||||||
@ -53,6 +56,17 @@ function build_context(array $config): Context {
|
|||||||
HttpDriver::class => function($c) {
|
HttpDriver::class => function($c) {
|
||||||
$config = $c->get('config');
|
$config = $c->get('config');
|
||||||
return HttpDrivers::getHttpDriver($config['upload_by_url_timeout'], $config['max_filesize']);
|
return HttpDrivers::getHttpDriver($config['upload_by_url_timeout'], $config['max_filesize']);
|
||||||
|
},
|
||||||
|
RemoteCaptchaQuery::class => function($c) {
|
||||||
|
$config = $c->get('config');
|
||||||
|
$http = $c->get(HttpDriver::class);
|
||||||
|
if ($config['recaptcha']) {
|
||||||
|
return new ReCaptchaQuery($http, $config['recaptcha_private']);
|
||||||
|
} elseif ($config['hcaptcha']) {
|
||||||
|
return new HCaptchaQuery($http, $config['hcaptcha_private'], $config['hcaptcha_public']);
|
||||||
|
} else {
|
||||||
|
throw new RuntimeException('No remote captcha service available');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
@ -6,68 +6,107 @@ use Vichan\Driver\HttpDriver;
|
|||||||
defined('TINYBOARD') or exit;
|
defined('TINYBOARD') or exit;
|
||||||
|
|
||||||
|
|
||||||
class RemoteCaptchaQuery {
|
class ReCaptchaQuery implements RemoteCaptchaQuery {
|
||||||
private HttpDriver $http;
|
private HttpDriver $http;
|
||||||
private string $secret;
|
private string $secret;
|
||||||
private string $endpoint;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new CaptchaRemoteQueries instance using the google recaptcha service.
|
* Creates a new ReCaptchaQuery using the google recaptcha service.
|
||||||
*
|
*
|
||||||
* @param HttpDriver $http The http client.
|
* @param HttpDriver $http The http client.
|
||||||
* @param string $secret Server side secret.
|
* @param string $secret Server side secret.
|
||||||
* @return CaptchaRemoteQueries A new captcha query instance.
|
* @return ReCaptchaQuery A new ReCaptchaQuery query instance.
|
||||||
*/
|
*/
|
||||||
public static function withRecaptcha(HttpDriver $http, string $secret): RemoteCaptchaQuery {
|
public function __construct(HttpDriver $http, string $secret) {
|
||||||
return new self($http, $secret, 'https://www.google.com/recaptcha/api/siteverify');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new CaptchaRemoteQueries instance using the hcaptcha service.
|
|
||||||
*
|
|
||||||
* @param HttpDriver $http The http client.
|
|
||||||
* @param string $secret Server side secret.
|
|
||||||
* @return CaptchaRemoteQueries A new captcha query instance.
|
|
||||||
*/
|
|
||||||
public static function withHCaptcha(HttpDriver $http, string $secret): RemoteCaptchaQuery {
|
|
||||||
return new self($http, $secret, 'https://hcaptcha.com/siteverify');
|
|
||||||
}
|
|
||||||
|
|
||||||
private function __construct(HttpDriver $http, string $secret, string $endpoint) {
|
|
||||||
$this->http = $http;
|
$this->http = $http;
|
||||||
$this->secret = $secret;
|
$this->secret = $secret;
|
||||||
$this->endpoint = $endpoint;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public function responseField(): string {
|
||||||
* Checks if the user at the remote ip passed the captcha.
|
return 'g-recaptcha-response';
|
||||||
*
|
}
|
||||||
* @param string $response User provided response.
|
|
||||||
* @param string $remote_ip User ip.
|
|
||||||
* @return bool Returns true if the user passed the captcha.
|
|
||||||
* @throws RuntimeException|JsonException Throws on IO errors or if it fails to decode the answer.
|
|
||||||
*/
|
|
||||||
public function verify(string $response, string $remote_ip): bool {
|
|
||||||
$data = array(
|
|
||||||
'secret' => $this->secret,
|
|
||||||
'response' => $response,
|
|
||||||
'remoteip' => $remote_ip
|
|
||||||
);
|
|
||||||
|
|
||||||
$ret = $this->http->requestGet($this->endpoint, $data);
|
public function verify(string $response, ?string $remote_ip): bool {
|
||||||
|
$data = [
|
||||||
|
'secret' => $this->secret,
|
||||||
|
'response' => $response
|
||||||
|
];
|
||||||
|
|
||||||
|
if ($remote_ip !== null) {
|
||||||
|
$data['remoteip'] = $remote_ip;
|
||||||
|
}
|
||||||
|
|
||||||
|
$ret = $this->http->requestGet('https://www.google.com/recaptcha/api/siteverify', $data);
|
||||||
$resp = json_decode($ret, true, 16, JSON_THROW_ON_ERROR);
|
$resp = json_decode($ret, true, 16, JSON_THROW_ON_ERROR);
|
||||||
|
|
||||||
return isset($resp['success']) && $resp['success'];
|
return isset($resp['success']) && $resp['success'];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class HCaptchaQuery implements RemoteCaptchaQuery {
|
||||||
|
private HttpDriver $http;
|
||||||
|
private string $secret;
|
||||||
|
private string $sitekey;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new HCaptchaQuery using the hCaptcha service.
|
||||||
|
*
|
||||||
|
* @param HttpDriver $http The http client.
|
||||||
|
* @param string $secret Server side secret.
|
||||||
|
* @return HCaptchaQuery A new hCaptcha query instance.
|
||||||
|
*/
|
||||||
|
public function __construct(HttpDriver $http, string $secret, string $sitekey) {
|
||||||
|
$this->http = $http;
|
||||||
|
$this->secret = $secret;
|
||||||
|
$this->sitekey = $sitekey;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function responseField(): string {
|
||||||
|
return 'h-captcha-response';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function verify(string $response, ?string $remote_ip): bool {
|
||||||
|
$data = [
|
||||||
|
'secret' => $this->secret,
|
||||||
|
'response' => $response,
|
||||||
|
'sitekey' => $this->sitekey
|
||||||
|
];
|
||||||
|
|
||||||
|
if ($remote_ip !== null) {
|
||||||
|
$data['remoteip'] = $remote_ip;
|
||||||
|
}
|
||||||
|
|
||||||
|
$ret = $this->http->requestGet('https://hcaptcha.com/siteverify', $data);
|
||||||
|
$resp = json_decode($ret, true, 16, JSON_THROW_ON_ERROR);
|
||||||
|
|
||||||
|
return isset($resp['success']) && $resp['success'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface RemoteCaptchaQuery {
|
||||||
|
/**
|
||||||
|
* Name of the response field in the form data expected by the implementation.
|
||||||
|
*
|
||||||
|
* @return string The name of the field.
|
||||||
|
*/
|
||||||
|
public function responseField(): string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the user at the remote ip passed the captcha.
|
||||||
|
*
|
||||||
|
* @param string $response User provided response.
|
||||||
|
* @param ?string $remote_ip User ip. Leave to null to only check the response value.
|
||||||
|
* @return bool Returns true if the user passed the captcha.
|
||||||
|
* @throws RuntimeException|JsonException Throws on IO errors or if it fails to decode the answer.
|
||||||
|
*/
|
||||||
|
public function verify(string $response, ?string $remote_ip): bool;
|
||||||
|
}
|
||||||
|
|
||||||
class NativeCaptchaQuery {
|
class NativeCaptchaQuery {
|
||||||
private HttpDriver $http;
|
private HttpDriver $http;
|
||||||
private string $domain;
|
private string $domain;
|
||||||
private string $provider_check;
|
private string $provider_check;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param HttpDriver $http The http client.
|
* @param HttpDriver $http The http client.
|
||||||
* @param string $domain The server's domain.
|
* @param string $domain The server's domain.
|
||||||
@ -89,12 +128,12 @@ class NativeCaptchaQuery {
|
|||||||
* @throws RuntimeException Throws on IO errors.
|
* @throws RuntimeException Throws on IO errors.
|
||||||
*/
|
*/
|
||||||
public function verify(string $extra, string $user_text, string $user_cookie): bool {
|
public function verify(string $extra, string $user_text, string $user_cookie): bool {
|
||||||
$data = array(
|
$data = [
|
||||||
'mode' => 'check',
|
'mode' => 'check',
|
||||||
'text' => $user_text,
|
'text' => $user_text,
|
||||||
'extra' => $extra,
|
'extra' => $extra,
|
||||||
'cookie' => $user_cookie
|
'cookie' => $user_cookie
|
||||||
);
|
];
|
||||||
|
|
||||||
$ret = $this->http->requestGet($this->domain . '/' . $this->provider_check, $data);
|
$ret = $this->http->requestGet($this->domain . '/' . $this->provider_check, $data);
|
||||||
return $ret === '1';
|
return $ret === '1';
|
||||||
|
28
post.php
28
post.php
@ -648,31 +648,21 @@ if (isset($_POST['delete'])) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Remote 3rd party captchas.
|
// Remote 3rd party captchas.
|
||||||
else if (!$config['dynamic_captcha'] || $config['dynamic_captcha'] === $_SERVER['REMOTE_ADDR']) {
|
elseif (($config['recaptcha'] || $config['hcaptcha'])
|
||||||
// recaptcha
|
&& (!$config['dynamic_captcha'] || $config['dynamic_captcha'] === $_SERVER['REMOTE_ADDR'])) {
|
||||||
if ($config['recaptcha']) {
|
$query = $content->get(RemoteCaptchaQuery::class);
|
||||||
if (!isset($_POST['g-recaptcha-response'])) {
|
$field = $query->responseField();
|
||||||
error($config['error']['bot']);
|
|
||||||
}
|
if (!isset($_POST[$field])) {
|
||||||
$response = $_POST['g-recaptcha-response'];
|
error($config['error']['bot']);
|
||||||
$query = RemoteCaptchaQuery::withRecaptcha($context->get(HttpDriver::class), $config['recaptcha_private']);
|
}
|
||||||
}
|
$response = $_POST[$field];
|
||||||
// hCaptcha
|
|
||||||
elseif ($config['hcaptcha']) {
|
|
||||||
if (!isset($_POST['h-captcha-response'])) {
|
|
||||||
error($config['error']['bot']);
|
|
||||||
}
|
|
||||||
$response = $_POST['h-captcha-response'];
|
|
||||||
$query = RemoteCaptchaQuery::withHCaptcha($context->get(HttpDriver::class), $config['hcaptcha_private']);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isset($query, $response)) {
|
|
||||||
$success = $query->verify($response, $_SERVER['REMOTE_ADDR']);
|
$success = $query->verify($response, $_SERVER['REMOTE_ADDR']);
|
||||||
if (!$success) {
|
if (!$success) {
|
||||||
error($config['error']['captcha']);
|
error($config['error']['captcha']);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
} catch (RuntimeException $e) {
|
} catch (RuntimeException $e) {
|
||||||
$context->getLog()->log(Log::ERROR, "Captcha IO exception: {$e->getMessage()}");
|
$context->getLog()->log(Log::ERROR, "Captcha IO exception: {$e->getMessage()}");
|
||||||
error($config['error']['remote_io_error']);
|
error($config['error']['remote_io_error']);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user