Skip to content

Commit b86e62c

Browse files
authored
Merge pull request #7029 from kenjis/fix-honeypot-csp-bug
fix: Honeypot field appears when CSP is enabled
2 parents a3e5f1d + 4a1d91e commit b86e62c

7 files changed

Lines changed: 96 additions & 20 deletions

File tree

app/Config/Honeypot.php

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,19 @@ class Honeypot extends BaseConfig
2424
/**
2525
* Honeypot HTML Template
2626
*/
27-
public string $template = '<label>{label}</label><input type="text" name="{name}" value=""/>';
27+
public string $template = '<label>{label}</label><input type="text" name="{name}" value="">';
2828

2929
/**
3030
* Honeypot container
31+
*
32+
* If you enabled CSP, you can remove `style="display:none"`.
3133
*/
3234
public string $container = '<div style="display:none">{template}</div>';
35+
36+
/**
37+
* The id attribute for Honeypot container tag
38+
*
39+
* Used when CSP is enabled.
40+
*/
41+
public string $containerId = 'hpc';
3342
}

system/Honeypot/Honeypot.php

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,8 @@ public function __construct(HoneypotConfig $config)
4646
$this->config->container = '<div style="display:none">{template}</div>';
4747
}
4848

49+
$this->config->containerId ??= 'hpc';
50+
4951
if ($this->config->template === '') {
5052
throw HoneypotException::forNoTemplate();
5153
}
@@ -70,10 +72,26 @@ public function hasContent(RequestInterface $request)
7072
*/
7173
public function attachHoneypot(ResponseInterface $response)
7274
{
75+
if ($response->getCSP()->enabled()) {
76+
// Add id attribute to the container tag.
77+
$this->config->container = str_ireplace(
78+
'>{template}',
79+
' id="' . $this->config->containerId . '">{template}',
80+
$this->config->container
81+
);
82+
}
83+
7384
$prepField = $this->prepareTemplate($this->config->template);
7485

7586
$body = $response->getBody();
7687
$body = str_ireplace('</form>', $prepField . '</form>', $body);
88+
89+
if ($response->getCSP()->enabled()) {
90+
// Add style tag for the container tag in the head tag.
91+
$style = '<style ' . csp_style_nonce() . '>#' . $this->config->containerId . ' { display:none }</style>';
92+
$body = str_ireplace('</head>', $style . '</head>', $body);
93+
}
94+
7795
$response->setBody($body);
7896
}
7997

tests/system/Honeypot/HoneypotTest.php

Lines changed: 32 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,16 @@
1111

1212
namespace CodeIgniter\Honeypot;
1313

14+
use CodeIgniter\Config\Factories;
1415
use CodeIgniter\Config\Services;
1516
use CodeIgniter\Filters\Filters;
1617
use CodeIgniter\Honeypot\Exceptions\HoneypotException;
1718
use CodeIgniter\HTTP\CLIRequest;
1819
use CodeIgniter\HTTP\IncomingRequest;
1920
use CodeIgniter\HTTP\Response;
2021
use CodeIgniter\Test\CIUnitTestCase;
22+
use Config\App;
23+
use Config\Honeypot as HoneypotConfig;
2124

2225
/**
2326
* @backupGlobals enabled
@@ -28,7 +31,7 @@
2831
*/
2932
final class HoneypotTest extends CIUnitTestCase
3033
{
31-
private \Config\Honeypot $config;
34+
private HoneypotConfig $config;
3235
private Honeypot $honeypot;
3336

3437
/**
@@ -41,14 +44,16 @@ final class HoneypotTest extends CIUnitTestCase
4144
protected function setUp(): void
4245
{
4346
parent::setUp();
44-
$this->config = new \Config\Honeypot();
47+
48+
$this->config = new HoneypotConfig();
4549
$this->honeypot = new Honeypot($this->config);
4650

4751
unset($_POST[$this->config->name]);
4852
$_SERVER['REQUEST_METHOD'] = 'POST';
4953
$_POST[$this->config->name] = 'hey';
50-
$this->request = Services::request(null, false);
51-
$this->response = Services::response();
54+
55+
$this->request = Services::request(null, false);
56+
$this->response = Services::response();
5257
}
5358

5459
public function testAttachHoneypot()
@@ -66,16 +71,35 @@ public function testAttachHoneypotAndContainer()
6671
{
6772
$this->response->setBody('<form></form>');
6873
$this->honeypot->attachHoneypot($this->response);
69-
$expected = '<form><div style="display:none"><label>Fill This Field</label><input type="text" name="honeypot" value=""/></div></form>';
74+
$expected = '<form><div style="display:none"><label>Fill This Field</label><input type="text" name="honeypot" value=""></div></form>';
7075
$this->assertSame($expected, $this->response->getBody());
7176

7277
$this->config->container = '<div class="hidden">{template}</div>';
7378
$this->response->setBody('<form></form>');
7479
$this->honeypot->attachHoneypot($this->response);
75-
$expected = '<form><div class="hidden"><label>Fill This Field</label><input type="text" name="honeypot" value=""/></div></form>';
80+
$expected = '<form><div class="hidden"><label>Fill This Field</label><input type="text" name="honeypot" value=""></div></form>';
7681
$this->assertSame($expected, $this->response->getBody());
7782
}
7883

84+
public function testAttachHoneypotAndContainerWithCSP()
85+
{
86+
$this->resetServices();
87+
88+
$config = new App();
89+
$config->CSPEnabled = true;
90+
Factories::injectMock('config', 'App', $config);
91+
$this->response = Services::response($config, false);
92+
93+
$this->config = new HoneypotConfig();
94+
$this->honeypot = new Honeypot($this->config);
95+
96+
$this->response->setBody('<head></head><body><form></form></body>');
97+
$this->honeypot->attachHoneypot($this->response);
98+
99+
$regex = '!<head><style nonce="[0-9a-f]+">#hpc { display:none }</style></head><body><form><div style="display:none" id="hpc"><label>Fill This Field</label><input type="text" name="honeypot" value=""></div></form></body>!u';
100+
$this->assertMatchesRegularExpression($regex, $this->response->getBody());
101+
}
102+
79103
public function testHasntContent()
80104
{
81105
unset($_POST[$this->config->name]);
@@ -147,7 +171,7 @@ public function testHoneypotFilterAfter()
147171

148172
public function testEmptyConfigContainer()
149173
{
150-
$config = new \Config\Honeypot();
174+
$config = new HoneypotConfig();
151175
$config->container = '';
152176
$honeypot = new Honeypot($config);
153177

@@ -159,7 +183,7 @@ public function testEmptyConfigContainer()
159183

160184
public function testNoTemplateConfigContainer()
161185
{
162-
$config = new \Config\Honeypot();
186+
$config = new HoneypotConfig();
163187
$config->container = '<div></div>';
164188
$honeypot = new Honeypot($config);
165189

user_guide_src/source/changelogs/v4.3.0.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -291,6 +291,7 @@ The following items are affected:
291291

292292
- Typography class: Creation of ``br`` tag
293293
- View Parser: The ``nl2br`` filter
294+
- Honeypot: ``input`` tag
294295
- Form helper
295296
- HTML helper
296297
- Common Functions
@@ -364,3 +365,4 @@ Bugs Fixed
364365
- Fixed a bug when all types of ``Prepared Queries`` were returning a ``Result`` object instead of a bool value for write-type queries.
365366
- Fixed a bug with variable filtering in JSON requests using with ``IncomingRequest::getVar()`` or ``IncomingRequest::getJsonVar()`` methods.
366367
- Fixed a bug when variable type may be changed when using a specified index with ``IncomingRequest::getVar()`` or ``IncomingRequest::getJsonVar()`` methods.
368+
- Fixed a bug that Honeypot field appears when CSP is enabled. See also :ref:`upgrade-430-honeypot-and-csp`.

user_guide_src/source/installation/upgrade_430.rst

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,16 @@ Database
216216
- The ``Model::update()`` method now raises a ``DatabaseException`` if it generates an SQL
217217
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)``.
218218

219+
.. _upgrade-430-honeypot-and-csp:
220+
221+
Honeypot and CSP
222+
================
223+
224+
When CSP is enabled, id attribute ``id="hpc"`` will be injected into the container tag
225+
for the Honeypot field to hide the field. If the id is already used in your views, you need to change it
226+
with ``Config\Honeypot::$containerId``.
227+
And you can remove ``style="display:none"`` in ``Config\Honeypot::$container``.
228+
219229
Others
220230
======
221231

@@ -260,6 +270,10 @@ Config
260270
- app/Config/Exceptions.php
261271
- Two additional public properties were added: ``$logDeprecations`` and ``$deprecationLogLevel``.
262272
See See :ref:`logging_deprecation_warnings` for details.
273+
- app/Config/Honeypot.php
274+
- The new property ``$containerId`` is added to set id attribute value for the container tag
275+
when CSP is enabled.
276+
- The ``input`` tag in the property ``$template`` value has been changed to HTML5 compatible.
263277
- app/Config/Logger.php
264278
- The property ``$threshold`` has been changed to ``9`` in other than ``production``
265279
environment.
Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,40 @@
1-
=====================
1+
##############
22
Honeypot Class
3-
=====================
3+
##############
44

55
The Honeypot Class makes it possible to determine when a Bot makes a request to a CodeIgniter4 application,
6-
if it's enabled in ``Application\Config\Filters.php`` file. This is done by attaching form fields to any form,
6+
if it's enabled in **app\Config\Filters.php** file. This is done by attaching form fields to any form,
77
and this form field is hidden from a human but accessible to a Bot. When data is entered into the field, it's
88
assumed the request is coming from a Bot, and you can throw a ``HoneypotException``.
99

1010
.. contents::
1111
:local:
1212
:depth: 2
1313

14+
*****************
1415
Enabling Honeypot
15-
=====================
16+
*****************
1617

1718
To enable a Honeypot, changes have to be made to the **app/Config/Filters.php**. Just uncomment honeypot
1819
from the ``$globals`` array, like:
1920

2021
.. literalinclude:: honeypot/001.php
2122

22-
A sample Honeypot filter is bundled, as ``system/Filters/Honeypot.php``.
23-
If it is not suitable, make your own at ``app/Filters/Honeypot.php``,
23+
A sample Honeypot filter is bundled, as **system/Filters/Honeypot.php**.
24+
If it is not suitable, make your own at **app/Filters/Honeypot.php**,
2425
and modify the ``$aliases`` in the configuration appropriately.
2526

27+
********************
2628
Customizing Honeypot
27-
=====================
29+
********************
2830

2931
Honeypot can be customized. The fields below can be set either in
3032
**app/Config/Honeypot.php** or in **.env**.
3133

32-
* ``hidden`` - true|false to control visibility of the honeypot field; default is ``true``
33-
* ``label`` - HTML label for the honeypot field, default is 'Fill This Field'
34-
* ``name`` - name of the HTML form field used for the template; default is 'honeypot'
35-
* ``template`` - form field template used for the honeypot; default is '<label>{label}</label><input type="text" name="{name}" value=""/>'
34+
* ``$hidden`` - ``true`` or ``false`` to control visibility of the honeypot field; default is ``true``
35+
* ``$label`` - HTML label for the honeypot field, default is ``'Fill This Field'``
36+
* ``$name`` - name of the HTML form field used for the template; default is ``'honeypot'``
37+
* ``$template`` - form field template used for the honeypot; default is ``'<label>{label}</label><input type="text" name="{name}" value="">'``
38+
* ``$container`` - container tag for the template; default is ``'<div style="display:none">{template}</div>'``.
39+
If you enables CSP, you can remove ``style="display:none"``.
40+
* ``$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'``

user_guide_src/source/libraries/honeypot/001.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,18 @@
66

77
class Filters extends BaseConfig
88
{
9+
// ...
10+
911
public $globals = [
1012
'before' => [
1113
'honeypot',
1214
// 'csrf',
15+
// 'invalidchars',
1316
],
1417
'after' => [
1518
'toolbar',
1619
'honeypot',
20+
// 'secureheaders',
1721
],
1822
];
1923

0 commit comments

Comments
 (0)