diff --git a/app/Config/Honeypot.php b/app/Config/Honeypot.php index 179b01546902..67ebcb0ee74c 100644 --- a/app/Config/Honeypot.php +++ b/app/Config/Honeypot.php @@ -24,10 +24,19 @@ class Honeypot extends BaseConfig /** * Honeypot HTML Template */ - public string $template = ''; + public string $template = ''; /** * Honeypot container + * + * If you enabled CSP, you can remove `style="display:none"`. */ public string $container = '
{template}
'; + + /** + * The id attribute for Honeypot container tag + * + * Used when CSP is enabled. + */ + public string $containerId = 'hpc'; } diff --git a/system/Honeypot/Honeypot.php b/system/Honeypot/Honeypot.php index fdd8fe95168b..7ab0cd36737e 100644 --- a/system/Honeypot/Honeypot.php +++ b/system/Honeypot/Honeypot.php @@ -46,6 +46,8 @@ public function __construct(HoneypotConfig $config) $this->config->container = '
{template}
'; } + $this->config->containerId ??= 'hpc'; + if ($this->config->template === '') { throw HoneypotException::forNoTemplate(); } @@ -70,10 +72,26 @@ public function hasContent(RequestInterface $request) */ public function attachHoneypot(ResponseInterface $response) { + if ($response->getCSP()->enabled()) { + // Add id attribute to the container tag. + $this->config->container = str_ireplace( + '>{template}', + ' id="' . $this->config->containerId . '">{template}', + $this->config->container + ); + } + $prepField = $this->prepareTemplate($this->config->template); $body = $response->getBody(); $body = str_ireplace('', $prepField . '', $body); + + if ($response->getCSP()->enabled()) { + // Add style tag for the container tag in the head tag. + $style = ''; + $body = str_ireplace('', $style . '', $body); + } + $response->setBody($body); } diff --git a/tests/system/Honeypot/HoneypotTest.php b/tests/system/Honeypot/HoneypotTest.php index 5b9c0c0073d8..b87ce3fe3c48 100644 --- a/tests/system/Honeypot/HoneypotTest.php +++ b/tests/system/Honeypot/HoneypotTest.php @@ -11,6 +11,7 @@ namespace CodeIgniter\Honeypot; +use CodeIgniter\Config\Factories; use CodeIgniter\Config\Services; use CodeIgniter\Filters\Filters; use CodeIgniter\Honeypot\Exceptions\HoneypotException; @@ -18,6 +19,8 @@ use CodeIgniter\HTTP\IncomingRequest; use CodeIgniter\HTTP\Response; use CodeIgniter\Test\CIUnitTestCase; +use Config\App; +use Config\Honeypot as HoneypotConfig; /** * @backupGlobals enabled @@ -28,7 +31,7 @@ */ final class HoneypotTest extends CIUnitTestCase { - private \Config\Honeypot $config; + private HoneypotConfig $config; private Honeypot $honeypot; /** @@ -41,14 +44,16 @@ final class HoneypotTest extends CIUnitTestCase protected function setUp(): void { parent::setUp(); - $this->config = new \Config\Honeypot(); + + $this->config = new HoneypotConfig(); $this->honeypot = new Honeypot($this->config); unset($_POST[$this->config->name]); $_SERVER['REQUEST_METHOD'] = 'POST'; $_POST[$this->config->name] = 'hey'; - $this->request = Services::request(null, false); - $this->response = Services::response(); + + $this->request = Services::request(null, false); + $this->response = Services::response(); } public function testAttachHoneypot() @@ -66,16 +71,35 @@ public function testAttachHoneypotAndContainer() { $this->response->setBody('
'); $this->honeypot->attachHoneypot($this->response); - $expected = '
'; + $expected = '
'; $this->assertSame($expected, $this->response->getBody()); $this->config->container = ''; $this->response->setBody('
'); $this->honeypot->attachHoneypot($this->response); - $expected = '
'; + $expected = '
'; $this->assertSame($expected, $this->response->getBody()); } + public function testAttachHoneypotAndContainerWithCSP() + { + $this->resetServices(); + + $config = new App(); + $config->CSPEnabled = true; + Factories::injectMock('config', 'App', $config); + $this->response = Services::response($config, false); + + $this->config = new HoneypotConfig(); + $this->honeypot = new Honeypot($this->config); + + $this->response->setBody('
'); + $this->honeypot->attachHoneypot($this->response); + + $regex = '!
!u'; + $this->assertMatchesRegularExpression($regex, $this->response->getBody()); + } + public function testHasntContent() { unset($_POST[$this->config->name]); @@ -147,7 +171,7 @@ public function testHoneypotFilterAfter() public function testEmptyConfigContainer() { - $config = new \Config\Honeypot(); + $config = new HoneypotConfig(); $config->container = ''; $honeypot = new Honeypot($config); @@ -159,7 +183,7 @@ public function testEmptyConfigContainer() public function testNoTemplateConfigContainer() { - $config = new \Config\Honeypot(); + $config = new HoneypotConfig(); $config->container = '
'; $honeypot = new Honeypot($config); diff --git a/user_guide_src/source/changelogs/v4.3.0.rst b/user_guide_src/source/changelogs/v4.3.0.rst index 7fdb8038d6d4..4ea3b567cf68 100644 --- a/user_guide_src/source/changelogs/v4.3.0.rst +++ b/user_guide_src/source/changelogs/v4.3.0.rst @@ -291,6 +291,7 @@ The following items are affected: - Typography class: Creation of ``br`` tag - View Parser: The ``nl2br`` filter +- Honeypot: ``input`` tag - Form helper - HTML helper - Common Functions @@ -364,3 +365,4 @@ Bugs Fixed - Fixed a bug when all types of ``Prepared Queries`` were returning a ``Result`` object instead of a bool value for write-type queries. - Fixed a bug with variable filtering in JSON requests using with ``IncomingRequest::getVar()`` or ``IncomingRequest::getJsonVar()`` methods. - Fixed a bug when variable type may be changed when using a specified index with ``IncomingRequest::getVar()`` or ``IncomingRequest::getJsonVar()`` methods. +- Fixed a bug that Honeypot field appears when CSP is enabled. See also :ref:`upgrade-430-honeypot-and-csp`. diff --git a/user_guide_src/source/installation/upgrade_430.rst b/user_guide_src/source/installation/upgrade_430.rst index 5d7192822c31..04f44a03f4b4 100644 --- a/user_guide_src/source/installation/upgrade_430.rst +++ b/user_guide_src/source/installation/upgrade_430.rst @@ -216,6 +216,16 @@ Database - The ``Model::update()`` method now raises a ``DatabaseException`` if it generates an SQL statement without a WHERE clause. If you need to update all records in a table, use Query Builder instead. E.g., ``$model->builder()->update($data)``. +.. _upgrade-430-honeypot-and-csp: + +Honeypot and CSP +================ + +When CSP is enabled, id attribute ``id="hpc"`` will be injected into the container tag +for the Honeypot field to hide the field. If the id is already used in your views, you need to change it +with ``Config\Honeypot::$containerId``. +And you can remove ``style="display:none"`` in ``Config\Honeypot::$container``. + Others ====== @@ -260,6 +270,10 @@ Config - app/Config/Exceptions.php - Two additional public properties were added: ``$logDeprecations`` and ``$deprecationLogLevel``. See See :ref:`logging_deprecation_warnings` for details. +- app/Config/Honeypot.php + - The new property ``$containerId`` is added to set id attribute value for the container tag + when CSP is enabled. + - The ``input`` tag in the property ``$template`` value has been changed to HTML5 compatible. - app/Config/Logger.php - The property ``$threshold`` has been changed to ``9`` in other than ``production`` environment. diff --git a/user_guide_src/source/libraries/honeypot.rst b/user_guide_src/source/libraries/honeypot.rst index cdaa4eb1eb52..e034e0dc08db 100644 --- a/user_guide_src/source/libraries/honeypot.rst +++ b/user_guide_src/source/libraries/honeypot.rst @@ -1,9 +1,9 @@ -===================== +############## Honeypot Class -===================== +############## The Honeypot Class makes it possible to determine when a Bot makes a request to a CodeIgniter4 application, -if it's enabled in ``Application\Config\Filters.php`` file. This is done by attaching form fields to any form, +if it's enabled in **app\Config\Filters.php** file. This is done by attaching form fields to any form, and this form field is hidden from a human but accessible to a Bot. When data is entered into the field, it's assumed the request is coming from a Bot, and you can throw a ``HoneypotException``. @@ -11,25 +11,30 @@ assumed the request is coming from a Bot, and you can throw a ``HoneypotExceptio :local: :depth: 2 +***************** Enabling Honeypot -===================== +***************** To enable a Honeypot, changes have to be made to the **app/Config/Filters.php**. Just uncomment honeypot from the ``$globals`` array, like: .. literalinclude:: honeypot/001.php -A sample Honeypot filter is bundled, as ``system/Filters/Honeypot.php``. -If it is not suitable, make your own at ``app/Filters/Honeypot.php``, +A sample Honeypot filter is bundled, as **system/Filters/Honeypot.php**. +If it is not suitable, make your own at **app/Filters/Honeypot.php**, and modify the ``$aliases`` in the configuration appropriately. +******************** Customizing Honeypot -===================== +******************** Honeypot can be customized. The fields below can be set either in **app/Config/Honeypot.php** or in **.env**. -* ``hidden`` - true|false to control visibility of the honeypot field; default is ``true`` -* ``label`` - HTML label for the honeypot field, default is 'Fill This Field' -* ``name`` - name of the HTML form field used for the template; default is 'honeypot' -* ``template`` - form field template used for the honeypot; default is '' +* ``$hidden`` - ``true`` or ``false`` to control visibility of the honeypot field; default is ``true`` +* ``$label`` - HTML label for the honeypot field, default is ``'Fill This Field'`` +* ``$name`` - name of the HTML form field used for the template; default is ``'honeypot'`` +* ``$template`` - form field template used for the honeypot; default is ``''`` +* ``$container`` - container tag for the template; default is ``'
{template}
'``. + If you enables CSP, you can remove ``style="display:none"``. +* ``$containerId`` - [Since v4.3.0] this setting is used only when you enables CSP. You can change the id attribute for the container tag; default is ``'hpc'`` diff --git a/user_guide_src/source/libraries/honeypot/001.php b/user_guide_src/source/libraries/honeypot/001.php index f0dbf7e64d57..573010145976 100644 --- a/user_guide_src/source/libraries/honeypot/001.php +++ b/user_guide_src/source/libraries/honeypot/001.php @@ -6,14 +6,18 @@ class Filters extends BaseConfig { + // ... + public $globals = [ 'before' => [ 'honeypot', // 'csrf', + // 'invalidchars', ], 'after' => [ 'toolbar', 'honeypot', + // 'secureheaders', ], ];