Skip to content

Commit

Permalink
support v3 better
Browse files Browse the repository at this point in the history
  • Loading branch information
sepiariver committed Jan 1, 2019
1 parent 4a1dd97 commit 5c6be80
Show file tree
Hide file tree
Showing 16 changed files with 388 additions and 111 deletions.
2 changes: 1 addition & 1 deletion _build/config.json
Expand Up @@ -3,7 +3,7 @@
"lowCaseName" : "recaptchav2",
"description" : "ReCaptchaV2 integrates V2 AND V3 of Google's ReCaptcha service into MODX as a FormIt hook.",
"author" : "YJ Tso @sepiariver",
"version" : "3.0.0-dev1",
"version" : "3.0.0-dev3",
"package" : {
"elements" : {
"snippets" : [
Expand Down
Binary file added _packages/recaptchav2-3.0.0-dev3.transport.zip
Binary file not shown.
@@ -1,7 +1,10 @@
<script src="https://www.google.com/recaptcha/api.js?render=[[+site_key]]&hl=[[++cultureKey]]"></script>
<input type="hidden" name="[[+token_key]]">
<input type="hidden" name="[[+action_key]]" value="[[+form_id]]">
<script>
grecaptcha.ready(function() {
grecaptcha.execute('[[+site_key]]', {action: '[[+form_id]]'});
grecaptcha.execute('[[+site_key]]', {action: '[[+form_id]]'}).then(function(token) {
document.querySelector('[name="[[+token_key]]"]').value = token;
});
});
</script>
Expand Up @@ -47,4 +47,4 @@
return true;
} else { // This works at least
return $recaptcha_html;
}
}
Expand Up @@ -8,7 +8,7 @@
* @link http://www.google.com/recaptcha
*
* Ported to MODX by YJ Tso @sepiariver
*
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
Expand All @@ -35,6 +35,7 @@
$lang = $modx->getOption('cultureKey', null, 'en');
// https://developers.google.com/recaptcha/docs/v3 "Actions"
$action_key = $modx->getOption('recaptchav3.action_key', null, 'recaptcha-action', true);
$token_key = $modx->getOption('recaptchav3.token_key', null, 'recaptcha-token', true);

// Options
if ($hook->formit) {
Expand All @@ -43,7 +44,7 @@
$threshold = floatval($modx->getOption('recaptchaThreshold', $properties, 0.7, true));
$ip = $modx->getOption('HTTP_CF_CONNECTING_IP', $_SERVER, $_SERVER['REMOTE_ADDR'], true);

// make sure the modLexicon class is loaded by instantiating
// make sure the modLexicon class is loaded by instantiating
$modx->getService('lexicon','modLexicon');
// load lexicon
$modx->lexicon->load('recaptchav2:default');
Expand All @@ -55,14 +56,14 @@
$recaptchaPath = $modx->getOption('recaptchav2.core_path', null, $modx->getOption('core_path') . 'components/recaptchav2/');
$recaptchaPath .= 'model/recaptchav2/';
if (!file_exists($recaptchaPath . 'autoload.php')) {
$modx->log(modX::LOG_LEVEL_ERROR, 'Cannot find required Recaptcha autoload.php file.');
$modx->log(modX::LOG_LEVEL_ERROR, 'Cannot find required Recaptcha autoload.php file.');
return false;
}
require_once($recaptchaPath . 'autoload.php');
$recaptcha = new \ReCaptcha\ReCaptcha($secret, new \ReCaptcha\RequestMethod\CurlPost());
if (!($recaptcha instanceof \ReCaptcha\ReCaptcha)) {
$hook->addError('recaptchav3_error', $tech_err_msg);
$modx->log(modX::LOG_LEVEL_ERROR, 'Failed to load Recaptcha class.');
$modx->log(modX::LOG_LEVEL_ERROR, 'Failed to load Recaptcha class.');
return false;
}

Expand All @@ -73,11 +74,11 @@
// Check if being used as hook
if (isset($hook)){
// Was there a reCAPTCHA response?
if ($hook->getValue('g-recaptcha-response')) {
if ($hook->getValue($token_key)) {
$resp = $recaptcha->setExpectedHostname($_SERVER['SERVER_NAME']) // MODX-y way?
->setExpectedAction($hook->getValue($action_key))
->setScoreThreshold($threshold)
->verify($hook->getValue('g-recaptcha-response'), $ip);
->verify($hook->getValue($token_key), $ip);
}

// Hook pass/fail
Expand Down
Expand Up @@ -34,18 +34,20 @@
$lang = $modx->getOption('cultureKey', null, 'en', true);
// https://developers.google.com/recaptcha/docs/v3 "Actions"
$action_key = $modx->getOption('recaptchav3.action_key', null, 'recaptcha-action', true);
$token_key = $modx->getOption('recaptchav3.token_key', null, 'recaptcha-token', true);
// new 'recaptchav3_html' Chunk
$tpl = $modx->getOption('tpl', $scriptProperties, 'recaptchav3_html', true);
$form_id = $modx->getOption('form_id', $scriptProperties, $modx->resource->get('uri'));

$recaptcha_html = $modx->getChunk($tpl, [
'site_key' => $site_key,
'lang' => $lang,
'form_id' => $form_id,
'form_id' => preg_replace('/[^A-Za-z\/_]/', '', $form_id),
'action_key' => $action_key,
'token_key' => $token_key,
]);

if ($hook) {
if ($hook) {
$hook->setValue('recaptchav3_html', $recaptcha_html); // This won't re-render on page reload there's validation errors
return true;
} else { // This works at least
Expand Down
192 changes: 178 additions & 14 deletions core/components/recaptchav2/model/recaptchav2/ReCaptcha/ReCaptcha.php 100644 → 100755
Expand Up @@ -3,7 +3,7 @@
* This is a PHP library that handles calling reCAPTCHA.
*
* @copyright Copyright (c) 2015, Google Inc.
* @link http://www.google.com/recaptcha
* @link https://www.google.com/recaptcha
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
Expand Down Expand Up @@ -35,11 +35,77 @@ class ReCaptcha
* Version of this client library.
* @const string
*/
const VERSION = 'php_1.1.2';
const VERSION = 'php_1.2.1';

/**
* URL for reCAPTCHA sitevrerify API
* @const string
*/
const SITE_VERIFY_URL = 'https://www.google.com/recaptcha/api/siteverify';

/**
* Invalid JSON received
* @const string
*/
const E_INVALID_JSON = 'invalid-json';

/**
* Could not connect to service
* @const string
*/
const E_CONNECTION_FAILED = 'connection-failed';

/**
* Did not receive a 200 from the service
* @const string
*/
const E_BAD_RESPONSE = 'bad-response';

/**
* Not a success, but no error codes received!
* @const string
*/
const E_UNKNOWN_ERROR = 'unknown-error';

/**
* ReCAPTCHA response not provided
* @const string
*/
const E_MISSING_INPUT_RESPONSE = 'missing-input-response';

/**
* Expected hostname did not match
* @const string
*/
const E_HOSTNAME_MISMATCH = 'hostname-mismatch';

/**
* Expected APK package name did not match
* @const string
*/
const E_APK_PACKAGE_NAME_MISMATCH = 'apk_package_name-mismatch';

/**
* Expected action did not match
* @const string
*/
const E_ACTION_MISMATCH = 'action-mismatch';

/**
* Score threshold not met
* @const string
*/
const E_SCORE_THRESHOLD_NOT_MET = 'score-threshold-not-met';

/**
* Challenge timeout
* @const string
*/
const E_CHALLENGE_TIMEOUT = 'challenge-timeout';

/**
* Shared secret for the site.
* @var type string
* @var string
*/
private $secret;

Expand All @@ -52,8 +118,9 @@ class ReCaptcha
/**
* Create a configured instance to use the reCAPTCHA service.
*
* @param string $secret shared secret between site and reCAPTCHA server.
* @param string $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($secret, RequestMethod $requestMethod = null)
{
Expand All @@ -66,32 +133,129 @@ public function __construct($secret, RequestMethod $requestMethod = null)
}

$this->secret = $secret;

if (!is_null($requestMethod)) {
$this->requestMethod = $requestMethod;
} else {
$this->requestMethod = new RequestMethod\Post();
}
$this->requestMethod = (is_null($requestMethod)) ? new RequestMethod\Post() : $requestMethod;
}

/**
* Calls the reCAPTCHA siteverify API to verify whether the user passes
* CAPTCHA test.
* CAPTCHA test and additionally runs any specified additional checks
*
* @param string $response The value of 'g-recaptcha-response' in the submitted form.
* @param string $response The user response token provided by reCAPTCHA, verifying the user on your site.
* @param string $remoteIp The end user's IP address.
* @return Response Response from the service.
*/
public function verify($response, $remoteIp = null)
{
// Discard empty solution submissions
if (empty($response)) {
$recaptchaResponse = new Response(false, array('missing-input-response'));
$recaptchaResponse = new Response(false, array(self::E_MISSING_INPUT_RESPONSE));
return $recaptchaResponse;
}

$params = new RequestParameters($this->secret, $response, $remoteIp, self::VERSION);
$rawResponse = $this->requestMethod->submit($params);
return Response::fromJson($rawResponse);
$initialResponse = Response::fromJson($rawResponse);
$validationErrors = array();

if (isset($this->hostname) && strcasecmp($this->hostname, $initialResponse->getHostname()) !== 0) {
$validationErrors[] = self::E_HOSTNAME_MISMATCH;
}

if (isset($this->apkPackageName) && strcasecmp($this->apkPackageName, $initialResponse->getApkPackageName()) !== 0) {
$validationErrors[] = self::E_APK_PACKAGE_NAME_MISMATCH;
}

if (isset($this->action) && strcasecmp($this->action, $initialResponse->getAction()) !== 0) {
$validationErrors[] = self::E_ACTION_MISMATCH;
}

if (isset($this->threshold) && $this->threshold > $initialResponse->getScore()) {
$validationErrors[] = self::E_SCORE_THRESHOLD_NOT_MET;
}

if (isset($this->timeoutSeconds)) {
$challengeTs = strtotime($initialResponse->getChallengeTs());

if ($challengeTs > 0 && time() - $challengeTs > $this->timeoutSeconds) {
$validationErrors[] = self::E_CHALLENGE_TIMEOUT;
}
}

if (empty($validationErrors)) {
return $initialResponse;
}

return new Response(
false,
array_merge($initialResponse->getErrorCodes(), $validationErrors),
$initialResponse->getHostname(),
$initialResponse->getChallengeTs(),
$initialResponse->getApkPackageName(),
$initialResponse->getScore(),
$initialResponse->getAction()
);
}

/**
* 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
* @return ReCaptcha Current instance for fluent interface
*/
public function setExpectedHostname($hostname)
{
$this->hostname = $hostname;
return $this;
}

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

/**
* Provide an action to match against in verify()
* This should be set per page.
*
* @param string $action Expected action
* @return ReCaptcha Current instance for fluent interface
*/
public function setExpectedAction($action)
{
$this->action = $action;
return $this;
}

/**
* 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
* @return ReCaptcha Current instance for fluent interface
*/
public function setScoreThreshold($threshold)
{
$this->threshold = floatval($threshold);
return $this;
}

/**
* Provide a timeout in seconds to test against the challenge timestamp in verify()
*
* @param int $timeoutSeconds Expected hostname
* @return ReCaptcha Current instance for fluent interface
*/
public function setChallengeTimeout($timeoutSeconds)
{
$this->timeoutSeconds = $timeoutSeconds;
return $this;
}
}
2 changes: 1 addition & 1 deletion core/components/recaptchav2/model/recaptchav2/ReCaptcha/RequestMethod.php 100644 → 100755
Expand Up @@ -3,7 +3,7 @@
* This is a PHP library that handles calling reCAPTCHA.
*
* @copyright Copyright (c) 2015, Google Inc.
* @link http://www.google.com/recaptcha
* @link https://www.google.com/recaptcha
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
Expand Down
2 changes: 1 addition & 1 deletion core/components/recaptchav2/model/recaptchav2/ReCaptcha/RequestMethod/Curl.php 100644 → 100755
Expand Up @@ -3,7 +3,7 @@
* This is a PHP library that handles calling reCAPTCHA.
*
* @copyright Copyright (c) 2015, Google Inc.
* @link http://www.google.com/recaptcha
* @link https://www.google.com/recaptcha
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
Expand Down

0 comments on commit 5c6be80

Please sign in to comment.