From 385311afda054039535f250e1c01a3eef29a99fe Mon Sep 17 00:00:00 2001
From: Ben Croker
Date: Wed, 17 Feb 2021 14:29:40 +0100
Subject: [PATCH] Added hashed value
---
src/Snaptcha.php | 2 -
src/migrations/Install.php | 18 ++-
.../m210216_120000_migrate_settings.php | 4 +-
.../m210217_120000_add_value_column.php | 52 ++++++
src/models/SettingsModel.php | 11 +-
src/models/SnaptchaModel.php | 7 +-
src/records/SnaptchaRecord.php | 1 +
src/services/SnaptchaService.php | 148 +++++++++++-------
src/templates/_error.html | 2 +-
src/variables/SnaptchaVariable.php | 15 +-
10 files changed, 186 insertions(+), 74 deletions(-)
create mode 100644 src/migrations/m210217_120000_add_value_column.php
diff --git a/src/Snaptcha.php b/src/Snaptcha.php
index 6653840..1221e3c 100644
--- a/src/Snaptcha.php
+++ b/src/Snaptcha.php
@@ -8,7 +8,6 @@
use Craft;
use craft\base\Plugin;
use craft\web\Controller;
-use craft\web\Request;
use craft\web\twig\variables\CraftVariable;
use craft\web\View;
use putyourlightson\snaptcha\models\SettingsModel;
@@ -16,7 +15,6 @@
use putyourlightson\snaptcha\variables\SnaptchaVariable;
use yii\base\ActionEvent;
use yii\base\Event;
-use yii\web\ForbiddenHttpException;
/**
* @property SnaptchaService $snaptcha
diff --git a/src/migrations/Install.php b/src/migrations/Install.php
index fa36984..ea7a281 100644
--- a/src/migrations/Install.php
+++ b/src/migrations/Install.php
@@ -7,7 +7,9 @@
use Craft;
use craft\db\Migration;
+use craft\helpers\StringHelper;
use putyourlightson\snaptcha\records\SnaptchaRecord;
+use putyourlightson\snaptcha\Snaptcha;
/**
* Install Migration
@@ -19,12 +21,13 @@ class Install extends Migration
*/
public function safeUp(): bool
{
- $snaptchaTable = SnaptchaRecord::tableName();
+ $table = SnaptchaRecord::tableName();
- if (!$this->db->tableExists($snaptchaTable)) {
- $this->createTable($snaptchaTable, [
+ if (!$this->db->tableExists($table)) {
+ $this->createTable($table, [
'id' => $this->primaryKey(),
'key' => $this->string(),
+ 'value' => $this->string(),
'ipAddress' => $this->string(),
'timestamp' => $this->integer(),
'expirationTime' => $this->integer(),
@@ -34,13 +37,18 @@ public function safeUp(): bool
'uid' => $this->uid(),
]);
- $this->createIndex(null, $snaptchaTable, 'key', false);
- $this->createIndex(null, $snaptchaTable, 'ipAddress', false);
+ $this->createIndex(null, $table, 'value', false);
+ $this->createIndex(null, $table, 'ipAddress', false);
// Refresh the db schema caches
Craft::$app->db->schema->refresh();
}
+ // Create and save default settings
+ $settings = Snaptcha::$plugin->settings;
+ $settings->salt = StringHelper::randomString(16);
+ Craft::$app->plugins->savePluginSettings(Snaptcha::$plugin, $settings->getAttributes());
+
return true;
}
diff --git a/src/migrations/m210216_120000_migrate_settings.php b/src/migrations/m210216_120000_migrate_settings.php
index 499c6d1..5ca04cc 100644
--- a/src/migrations/m210216_120000_migrate_settings.php
+++ b/src/migrations/m210216_120000_migrate_settings.php
@@ -7,6 +7,7 @@
use Craft;
use craft\db\Migration;
+use craft\helpers\StringHelper;
use putyourlightson\snaptcha\models\SettingsModel;
use putyourlightson\snaptcha\Snaptcha;
@@ -26,8 +27,9 @@ public function safeUp(): bool
// Resave plugin settings
$settings = Snaptcha::$plugin->settings;
+ $settings->salt = StringHelper::randomString(16);
- // Only update if original is unchanged
+ // Only update if original message is unchanged
if ($settings->errorMessage == 'Sorry, you have failed the security test. Please ensure that you have javascript enabled and that you refresh the page that you are trying to submit.') {
$settings->errorMessage = (new SettingsModel())->errorMessage;
}
diff --git a/src/migrations/m210217_120000_add_value_column.php b/src/migrations/m210217_120000_add_value_column.php
new file mode 100644
index 0000000..7a65a05
--- /dev/null
+++ b/src/migrations/m210217_120000_add_value_column.php
@@ -0,0 +1,52 @@
+projectConfig
+ ->get('plugins.snaptcha.schemaVersion', true);
+
+ if (!version_compare($schemaVersion, '3.0.0', '<')) {
+ return true;
+ }
+
+ $table = SnaptchaRecord::tableName();
+
+ // Delete all rows to avoid having stale values in the DB
+ $this->delete($table);
+
+ if (!$this->db->columnExists($table, 'value')) {
+ $this->addColumn($table, 'value', $this->string()->after('key'));
+
+ $this->createIndex(null, $table, 'value', false);
+
+ MigrationHelper::dropIndexIfExists($table, 'key');
+ }
+
+ return true;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function safeDown(): bool
+ {
+ echo "m181009_120000_update_blacklist_settings cannot be reverted.\n";
+
+ return false;
+ }
+}
diff --git a/src/models/SettingsModel.php b/src/models/SettingsModel.php
index cb38d00..b252544 100644
--- a/src/models/SettingsModel.php
+++ b/src/models/SettingsModel.php
@@ -29,6 +29,11 @@ class SettingsModel extends Model
*/
public $fieldName = 'snaptcha';
+ /**
+ * @var string
+ */
+ public $salt = '';
+
/**
* @var string
*/
@@ -65,17 +70,17 @@ class SettingsModel extends Model
public $minimumSubmitTime = 3;
/**
- * @var array
+ * @var array|string
*/
public $excludeControllerActions = [];
/**
- * @var array
+ * @var array|string
*/
public $allowList = [];
/**
- * @var array
+ * @var array|string
*/
public $denyList = [];
diff --git a/src/models/SnaptchaModel.php b/src/models/SnaptchaModel.php
index 1f5a174..b1ca079 100644
--- a/src/models/SnaptchaModel.php
+++ b/src/models/SnaptchaModel.php
@@ -14,6 +14,11 @@ class SnaptchaModel extends Model
*/
public $key;
+ /**
+ * @var string
+ */
+ public $value;
+
/**
* @var string
*/
@@ -40,7 +45,7 @@ class SnaptchaModel extends Model
public function rules(): array
{
return [
- [['key', 'ipAddress'], 'required'],
+ [['key', 'value', 'ipAddress'], 'required'],
[['timestamp', 'expirationTime', 'minimumSubmitTime'], 'integer'],
];
}
diff --git a/src/records/SnaptchaRecord.php b/src/records/SnaptchaRecord.php
index a41d393..4dc3e42 100755
--- a/src/records/SnaptchaRecord.php
+++ b/src/records/SnaptchaRecord.php
@@ -10,6 +10,7 @@
/**
* @property int $id
* @property string $key
+ * @property string $value
* @property string $ipAddress
* @property int $timestamp
* @property int|null $expirationTime
diff --git a/src/services/SnaptchaService.php b/src/services/SnaptchaService.php
index 323a16b..c1cf14c 100644
--- a/src/services/SnaptchaService.php
+++ b/src/services/SnaptchaService.php
@@ -42,54 +42,29 @@ class SnaptchaService extends Component
];
/**
- * Returns a field value.
+ * Returns a field key.
*
* @param SnaptchaModel $model
- *
* @return string|null
*/
- public function getFieldValue(SnaptchaModel $model)
+ public function getFieldKey(SnaptchaModel $model)
{
- $now = time();
- $hashedIpAddress = $this->_getHashedIpAddress();
-
- // Get most recent record with IP address from DB
- /** @var SnaptchaRecord|null $record */
- $record = SnaptchaRecord::find()
- ->where(['ipAddress' => $hashedIpAddress])
- ->orderBy('timestamp desc')
- ->one();
-
- // If record does not exist or one time key is enabled or the expiration time has passed
- if ($record === null || Snaptcha::$plugin->settings->oneTimeKey || $record->timestamp + ($record->expirationTime * 60) < $now) {
- // Set key to random string
- $model->key = StringHelper::randomString();
-
- // Hash IP address for privacy
- $model->ipAddress = $hashedIpAddress;
-
- // Set timestamp to current time
- $model->timestamp = $now;
-
- // Set optional fields from settings if not defined
- $model->expirationTime = $model->expirationTime ?? Snaptcha::$plugin->settings->expirationTime;
- $model->minimumSubmitTime = $model->minimumSubmitTime ?? Snaptcha::$plugin->settings->minimumSubmitTime;
-
- if (!$model->validate()) {
- return null;
- }
-
- $record = new SnaptchaRecord($model);
- }
+ $record = $this->_getSnaptchaRecord($model);
- // Refresh timestamp
- $record->timestamp = $now;
+ return $record ? $record->key : null;
+ }
- if (!$record->save()) {
- return null;
- }
+ /**
+ * Returns a field value.
+ *
+ * @param SnaptchaModel $model
+ * @return string|null
+ */
+ public function getFieldValue(SnaptchaModel $model)
+ {
+ $record = $this->_getSnaptchaRecord($model);
- return $record->key;
+ return $record ? $record->value : null;
}
/**
@@ -188,7 +163,7 @@ public function validateField(string $value = null): bool
/** @var SnaptchaRecord|null $record */
$record = SnaptchaRecord::find()
->where([
- 'key' => $value,
+ 'value' => $value,
'ipAddress' => $this->_getHashedIpAddress(),
])
->one();
@@ -234,30 +209,78 @@ public function validateField(string $value = null): bool
}
/**
- * Returns the current user's hashed IP address.
+ * Returns a Snaptcha record.
*
- * @return string
+ * @param SnaptchaModel $model
+ * @return SnaptchaRecord|null
*/
- private function _getHashedIpAddress(): string
+ private function _getSnaptchaRecord(SnaptchaModel $model)
{
- $ipAddress = Craft::$app->getRequest()->getUserIP();
+ $now = time();
+ $hashedIpAddress = $this->_getHashedIpAddress();
- return $ipAddress === null ? '' : md5($ipAddress);
+ // Get most recent record with IP address from DB
+ /** @var SnaptchaRecord|null $record */
+ $record = SnaptchaRecord::find()
+ ->where(['ipAddress' => $hashedIpAddress])
+ ->orderBy('timestamp desc')
+ ->one();
+
+ // If record does not exist or one time key is enabled or the expiration time has passed
+ if ($record === null || Snaptcha::$plugin->settings->oneTimeKey || $record->timestamp + ($record->expirationTime * 60) < $now) {
+ // Set key to random string
+ $model->key = StringHelper::randomString(16);
+ $model->value = $this->_getHashedValue($model->key, Snaptcha::$plugin->settings->salt);
+
+ // Hash IP address for privacy
+ $model->ipAddress = $hashedIpAddress;
+
+ // Set timestamp to current time
+ $model->timestamp = $now;
+
+ // Set optional fields from settings if not defined
+ $model->expirationTime = $model->expirationTime ?? Snaptcha::$plugin->settings->expirationTime;
+ $model->minimumSubmitTime = $model->minimumSubmitTime ?? Snaptcha::$plugin->settings->minimumSubmitTime;
+
+ if (!$model->validate()) {
+ return null;
+ }
+
+ $record = new SnaptchaRecord($model);
+ }
+
+ // Refresh timestamp
+ $record->timestamp = $now;
+
+ if (!$record->save()) {
+ return null;
+ }
+
+ return $record;
}
/**
- * Rejects and logs a form submission.
+ * Returns the hashed value.
*
- * @param string $message
- * @param array $params
+ * @param string $key
+ * @param string $salt
+ * @return string
*/
- private function _reject(string $message, array $params = [])
+ private function _getHashedValue(string $key, string $salt)
{
- if (Snaptcha::$plugin->settings->logRejected) {
- $url = Craft::$app->getRequest()->getAbsoluteUrl();
- $message = Craft::t('snaptcha', $message, $params).' ['.$url.']';
- LogToFile::log($message, 'snaptcha');
- }
+ return base64_encode($key.$salt);
+ }
+
+ /**
+ * Returns the current user's hashed IP address.
+ *
+ * @return string
+ */
+ private function _getHashedIpAddress(): string
+ {
+ $ipAddress = Craft::$app->getRequest()->getUserIP();
+
+ return $ipAddress === null ? '' : md5($ipAddress);
}
/**
@@ -284,4 +307,19 @@ private function _getNormalizedArray($values): array
return $values;
}
+
+ /**
+ * Rejects and logs a form submission.
+ *
+ * @param string $message
+ * @param array $params
+ */
+ private function _reject(string $message, array $params = [])
+ {
+ if (Snaptcha::$plugin->settings->logRejected) {
+ $url = Craft::$app->getRequest()->getAbsoluteUrl();
+ $message = Craft::t('snaptcha', $message, $params).' ['.$url.']';
+ LogToFile::log($message, 'snaptcha');
+ }
+ }
}
diff --git a/src/templates/_error.html b/src/templates/_error.html
index caafd72..aacef1f 100644
--- a/src/templates/_error.html
+++ b/src/templates/_error.html
@@ -21,7 +21,7 @@ {{ errorTitle }}