From a12c409162410c38b78a51e2d7a5e1d36c112bc5 Mon Sep 17 00:00:00 2001 From: Nwoke David Udoka Date: Sun, 3 Jun 2018 08:10:17 +0100 Subject: [PATCH 1/6] Honeypot Implementation --- application/Config/Filters.php | 7 +- application/Config/Honeypot.php | 25 +++ application/Filters/Honeypot.php | 47 ++++++ system/Honeypot/Honeypoter.php | 203 +++++++++++++++++++++++++ tests/system/Honeypot/HoneypotTest.php | 46 ++++++ 5 files changed, 326 insertions(+), 2 deletions(-) create mode 100644 application/Config/Honeypot.php create mode 100644 application/Filters/Honeypot.php create mode 100644 system/Honeypot/Honeypoter.php create mode 100644 tests/system/Honeypot/HoneypotTest.php diff --git a/application/Config/Filters.php b/application/Config/Filters.php index cb16f8137b64..a3ed0861b9d7 100644 --- a/application/Config/Filters.php +++ b/application/Config/Filters.php @@ -9,15 +9,18 @@ class Filters extends BaseConfig public $aliases = [ 'csrf' => \App\Filters\CSRF::class, 'toolbar' => \App\Filters\DebugToolbar::class, + 'honeypot' => \App\Filters\Honeypot::class ]; // Always applied before every request public $globals = [ 'before' => [ - // 'csrf' + //'honeypot' + // 'csrf', ], 'after' => [ - 'toolbar' + 'toolbar', + //'honeypot' ] ]; diff --git a/application/Config/Honeypot.php b/application/Config/Honeypot.php new file mode 100644 index 000000000000..ef603c710658 --- /dev/null +++ b/application/Config/Honeypot.php @@ -0,0 +1,25 @@ +template = ($honeypotConfig->template === '') ? + $this->getDefaultTemplate(): $honeypotConfig->template; + + $this->name = ($honeypotConfig->name === '') ? + $this->getDefaultName(): $honeypotConfig->name; + + $this->label = ($honeypotConfig->label === '') ? + $this->getDefaultLabel(): $honeypotConfig->label; + + } + + //-------------------------------------------------------------------- + + /** + * Checks the request if honeypot field has data. + * + * @param \CodeIgniter\HTTP\RequestInterface $request + * + */ + static function honeypotHasContent(RequestInterface $request) + { + self::$selfObject = (self::$selfObject === null) ? + new self(): self::$selfObject; + + // TODO Will there be need to protect against bad data? + if($request->getVar(self::$selfObject->name)){ + + return true; + } + + if($request->getGet(self::$selfObject->name)){ + + return true; + } + + if($request->getPost(self::$selfObject->name)){ + return true; + + } + return false; + } + + /** + * Attachs Honeypot template to response. + * + * @param \CodeIgniter\HTTP\RequestInterface $request + * @param \CodeIgniter\HTTP\ResponseInterface $response + */ + static function attachHoneypot(ResponseInterface $response) + { + self::$selfObject = (self::$selfObject === null) ? + new self(): self::$selfObject; + + $prep_field = self::$selfObject->prepareTemplate(self::$selfObject->template); + $style = self::$selfObject->getStyle(); + + $body = $response->getBody(); + $body = preg_replace('/<\/form>/', $prep_field, $body); + $body = preg_replace('/<\/head>/', $style, $body); + $response->setBody($body); + } + + /** + * Prepares the template by adding label + * content and field name. + * + * @param string $template + * @return string + */ + protected function prepareTemplate($template): string + { + $template = preg_replace('/{label}/', $this->label, $template); + $template = preg_replace('/{name}/', $this->name, $template); + return $template; + } + + /** + * Returns the default template which + * is used when the user enters none. + * + * @return string + */ + protected function getDefaultTemplate(): string + { + return ' + '; + } + + /** + * Returns the css style to hide the + * Honeypot template. + * + * @return string + */ + protected function getStyle(): string + { + return ' + '; + } + + /** + * Returns default label for the template + * which is used when user enters none + * + * @return string + */ + protected function getDefaultLabel(): string + { + return 'Fill This Field'; + } + + /** + * Returns default field name for the template + * which is used when user enters none + * + * @return string + */ + protected function getDefaultName(): string + { + return 'honeypot'; + } + + +} \ No newline at end of file diff --git a/tests/system/Honeypot/HoneypotTest.php b/tests/system/Honeypot/HoneypotTest.php new file mode 100644 index 000000000000..6ace4a86b640 --- /dev/null +++ b/tests/system/Honeypot/HoneypotTest.php @@ -0,0 +1,46 @@ +request = new IncomingRequest(new App(), + new \CodeIgniter\HTTP\URI(), + null, + new \CodeIgniter\HTTP\UserAgent() + ); + $this->response = Services::response(); + } + + public function testAttachHoneypot() + { + + $this->response->setBody('
'); + Honeypoter::attachHoneypot($this->response); + $this->assertContains('honeypot',$this->response->getBody()); + $this->response->setBody('
'); + $this->assertNotContains('honeypot',$this->response->getBody()); + } + + public function testCheckHoneypot() + { + + $_REQUEST['honeypot'] = 'hey'; + $this->assertEquals(true, Honeypoter::honeypotHasContent($this->request)); + $_POST['honeypot'] = 'hey'; + $this->assertEquals(true, Honeypoter::honeypotHasContent($this->request)); + $_GET['honeypot'] = 'hey'; + $this->assertEquals(true, Honeypoter::honeypotHasContent($this->request)); + } +} \ No newline at end of file From 8e0c7d4fbee5d551e449ea0660e063c771a2857c Mon Sep 17 00:00:00 2001 From: Nwoke David Udoka Date: Tue, 5 Jun 2018 16:11:28 +0100 Subject: [PATCH 2/6] Honeypot Implementation --- application/Config/Honeypot.php | 6 + application/Config/Services.php | 9 ++ application/Filters/Honeypot.php | 9 +- .../Honeypot/Exceptions/HoneypotException.php | 23 ++++ .../Honeypot/{Honeypoter.php => Honeypot.php} | 122 +++++------------- tests/system/Honeypot/HoneypotTest.php | 27 ++-- 6 files changed, 86 insertions(+), 110 deletions(-) create mode 100644 system/Honeypot/Exceptions/HoneypotException.php rename system/Honeypot/{Honeypoter.php => Honeypot.php} (50%) diff --git a/application/Config/Honeypot.php b/application/Config/Honeypot.php index ef603c710658..6f9b424a8fe1 100644 --- a/application/Config/Honeypot.php +++ b/application/Config/Honeypot.php @@ -5,6 +5,12 @@ class Honeypot extends BaseConfig { + /** + * Makes Honeypot visible or not to human + * + * @var boolean + */ + public $hidden = ''; /** * Honeypot Label Content * @var String diff --git a/application/Config/Services.php b/application/Config/Services.php index 543c2c4aa68b..918c8b92969e 100644 --- a/application/Config/Services.php +++ b/application/Config/Services.php @@ -30,5 +30,14 @@ class Services extends CoreServices // return new \CodeIgniter\Example(); // } + public static function honeypot($getShared = true) + { + if ($getShared) + { + return self::getSharedInstance('honeypot'); + } + return new \CodeIgniter\Honeypot\Honeypot(); + } + } diff --git a/application/Filters/Honeypot.php b/application/Filters/Honeypot.php index 5ceb43d0ded3..9b955e5f5573 100644 --- a/application/Filters/Honeypot.php +++ b/application/Filters/Honeypot.php @@ -3,7 +3,7 @@ use CodeIgniter\Filters\FilterInterface; use CodeIgniter\HTTP\RequestInterface; use CodeIgniter\HTTP\ResponseInterface; -use CodeIgniter\Honeypot\Honeypoter; +use Config\Services; class Honeypot implements FilterInterface { @@ -22,10 +22,10 @@ public function before (RequestInterface $request) { // Checks honeypot field if value was entered then show blank if so. - if(Honeypoter::honeypotHasContent($request)) + $honeypot = Services::honeypot(); + if($honeypot->hasContent($request)) { die(); - //Is Redirection needed } } @@ -42,6 +42,7 @@ public function before (RequestInterface $request) public function after (RequestInterface $request, ResponseInterface $response) { - Honeypoter::attachHoneypot($response); + $honeypot = Services::honeypot(); + $honeypot->attachHoneypot($response); } } diff --git a/system/Honeypot/Exceptions/HoneypotException.php b/system/Honeypot/Exceptions/HoneypotException.php new file mode 100644 index 000000000000..1d9948583ff7 --- /dev/null +++ b/system/Honeypot/Exceptions/HoneypotException.php @@ -0,0 +1,23 @@ +template = ($honeypotConfig->template === '') ? - $this->getDefaultTemplate(): $honeypotConfig->template; + $this->honeypotConfig = new HoneypotConfig(); - $this->name = ($honeypotConfig->name === '') ? - $this->getDefaultName(): $honeypotConfig->name; + if($this->honeypotConfig->hidden === '') + { + throw HoneypotException::forNoHiddenValue(); + } - $this->label = ($honeypotConfig->label === '') ? - $this->getDefaultLabel(): $honeypotConfig->label; + if($this->honeypotConfig->template === '') + { + throw HoneypotException::forNoTemplate(); + } + if($this->honeypotConfig->name === '') + { + throw HoneypotException::forNoNameField(); + } } //-------------------------------------------------------------------- @@ -91,46 +98,26 @@ function __construct () { * @param \CodeIgniter\HTTP\RequestInterface $request * */ - static function honeypotHasContent(RequestInterface $request) + public function hasContent(RequestInterface $request) { - self::$selfObject = (self::$selfObject === null) ? - new self(): self::$selfObject; - - // TODO Will there be need to protect against bad data? - if($request->getVar(self::$selfObject->name)){ - + if($request->getVar($this->honeypotConfig->name)) + { return true; } - - if($request->getGet(self::$selfObject->name)){ - - return true; - } - - if($request->getPost(self::$selfObject->name)){ - return true; - - } return false; } /** * Attachs Honeypot template to response. * - * @param \CodeIgniter\HTTP\RequestInterface $request * @param \CodeIgniter\HTTP\ResponseInterface $response */ - static function attachHoneypot(ResponseInterface $response) + public function attachHoneypot(ResponseInterface $response) { - self::$selfObject = (self::$selfObject === null) ? - new self(): self::$selfObject; - - $prep_field = self::$selfObject->prepareTemplate(self::$selfObject->template); - $style = self::$selfObject->getStyle(); + $prep_field = $this->prepareTemplate($this->honeypotConfig->template); $body = $response->getBody(); - $body = preg_replace('/<\/form>/', $prep_field, $body); - $body = preg_replace('/<\/head>/', $style, $body); + $body = str_ireplace('', $prep_field, $body); $response->setBody($body); } @@ -143,61 +130,14 @@ static function attachHoneypot(ResponseInterface $response) */ protected function prepareTemplate($template): string { - $template = preg_replace('/{label}/', $this->label, $template); - $template = preg_replace('/{name}/', $this->name, $template); - return $template; - } - - /** - * Returns the default template which - * is used when the user enters none. - * - * @return string - */ - protected function getDefaultTemplate(): string - { - return ' - '; - } - - /** - * Returns the css style to hide the - * Honeypot template. - * - * @return string - */ - protected function getStyle(): string - { - return ' - '; - } + $template = str_ireplace('{label}', $this->honeypotConfig->label, $template); + $template = str_ireplace('{name}', $this->honeypotConfig->name, $template); - /** - * Returns default label for the template - * which is used when user enters none - * - * @return string - */ - protected function getDefaultLabel(): string - { - return 'Fill This Field'; - } - - /** - * Returns default field name for the template - * which is used when user enters none - * - * @return string - */ - protected function getDefaultName(): string - { - return 'honeypot'; + if($this->honeypotConfig->hidden) + { + $template = '
'. $template . '
'; + } + return $template; } - } \ No newline at end of file diff --git a/tests/system/Honeypot/HoneypotTest.php b/tests/system/Honeypot/HoneypotTest.php index 6ace4a86b640..1efc32f12a67 100644 --- a/tests/system/Honeypot/HoneypotTest.php +++ b/tests/system/Honeypot/HoneypotTest.php @@ -1,46 +1,43 @@ request = new IncomingRequest(new App(), - new \CodeIgniter\HTTP\URI(), - null, - new \CodeIgniter\HTTP\UserAgent() - ); + $this->request = Services::request(); $this->response = Services::response(); + $this->honeypot = new Honeypot(); + } public function testAttachHoneypot() { $this->response->setBody('
'); - Honeypoter::attachHoneypot($this->response); - $this->assertContains('honeypot',$this->response->getBody()); + $this->honeypot->attachHoneypot($this->response); + $this->assertContains('honeypot', $this->response->getBody()); $this->response->setBody('
'); - $this->assertNotContains('honeypot',$this->response->getBody()); + $this->assertNotContains('honeypot', $this->response->getBody()); } - public function testCheckHoneypot() + public function testHasHoneypot() { $_REQUEST['honeypot'] = 'hey'; - $this->assertEquals(true, Honeypoter::honeypotHasContent($this->request)); + $this->assertEquals(true, $this->honeypot->hasContent($this->request)); $_POST['honeypot'] = 'hey'; - $this->assertEquals(true, Honeypoter::honeypotHasContent($this->request)); + $this->assertEquals(true, $this->honeypot->hasContent($this->request)); $_GET['honeypot'] = 'hey'; - $this->assertEquals(true, Honeypoter::honeypotHasContent($this->request)); + $this->assertEquals(true, $this->honeypot->hasContent($this->request)); } } \ No newline at end of file From 696227d8b4ab34fdfd7fedfb0020d7554fcf7dfc Mon Sep 17 00:00:00 2001 From: Nwoke David Udoka Date: Tue, 5 Jun 2018 21:22:57 +0100 Subject: [PATCH 3/6] Honeypot Implementation - added honeypot config to env --- env | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/env b/env index 816e118faeea..d5559450fb3f 100644 --- a/env +++ b/env @@ -76,3 +76,12 @@ # contentsecuritypolicy.reportURI = null # contentsecuritypolicy.sandbox = false # contentsecuritypolicy.upgradeInsecureRequests = false + +#-------------------------------------------------------------------- +# HONEYPOT +#-------------------------------------------------------------------- + +# honeypot.hidden = 'true' +# honeypot.label = 'Fill This Field' +# honeypot.name = 'honeypot' +# honeypot.template = '' From 3d04f1b4becd3dd46fa24111abdc8c195dc3aefb Mon Sep 17 00:00:00 2001 From: Nwoke David Udoka Date: Fri, 8 Jun 2018 19:46:27 +0100 Subject: [PATCH 4/6] Honeypot Implementation - Modification with Documentation --- application/Config/Honeypot.php | 8 +- application/Config/Services.php | 11 +-- application/Filters/Honeypot.php | 9 +- .../Honeypot/Exceptions/HoneypotException.php | 5 ++ system/Honeypot/Honeypot.php | 22 ++--- tests/system/Honeypot/HoneypotTest.php | 3 +- user_guide_src/source/index.rst | 5 +- user_guide_src/source/libraries/honeypot.rst | 87 +++++++++++++++++++ 8 files changed, 122 insertions(+), 28 deletions(-) create mode 100644 user_guide_src/source/libraries/honeypot.rst diff --git a/application/Config/Honeypot.php b/application/Config/Honeypot.php index 6f9b424a8fe1..d35b0c7ee861 100644 --- a/application/Config/Honeypot.php +++ b/application/Config/Honeypot.php @@ -10,22 +10,22 @@ class Honeypot extends BaseConfig * * @var boolean */ - public $hidden = ''; + public $hidden = true; /** * Honeypot Label Content * @var String */ - public $label = ''; + public $label = 'Fill This Field'; /** * Honeypot Field Name * @var String */ - public $name = ''; + public $name = 'honeypot'; /** * Honeypot HTML Template * @var String */ - public $template = ''; + public $template = ''; } \ No newline at end of file diff --git a/application/Config/Services.php b/application/Config/Services.php index 918c8b92969e..b46d657fee60 100644 --- a/application/Config/Services.php +++ b/application/Config/Services.php @@ -1,6 +1,7 @@ hasContent($request)) { - die(); + throw HoneypotException::isBot(); } } @@ -42,7 +44,8 @@ public function before (RequestInterface $request) public function after (RequestInterface $request, ResponseInterface $response) { - $honeypot = Services::honeypot(); + + $honeypot = Services::honeypot(new \Config\Honeypot()); $honeypot->attachHoneypot($response); } } diff --git a/system/Honeypot/Exceptions/HoneypotException.php b/system/Honeypot/Exceptions/HoneypotException.php index 1d9948583ff7..c6085655c905 100644 --- a/system/Honeypot/Exceptions/HoneypotException.php +++ b/system/Honeypot/Exceptions/HoneypotException.php @@ -20,4 +20,9 @@ public static function forNoHiddenValue() return new static(lang('Honeypot.noHiddenValue')); } + public static function isBot() + { + return new static(lang('Honeypot.theClientIsABot')); + } + } diff --git a/system/Honeypot/Honeypot.php b/system/Honeypot/Honeypot.php index af88dea99f26..22ccb7084c0d 100644 --- a/system/Honeypot/Honeypot.php +++ b/system/Honeypot/Honeypot.php @@ -67,24 +67,24 @@ class Honeypot * Self Instance of Class * @var Honeypot */ - protected $honeypotConfig; + protected $config; //-------------------------------------------------------------------- - function __construct () { - $this->honeypotConfig = new HoneypotConfig(); + function __construct (BaseConfig $config) { + $this->config = $config; - if($this->honeypotConfig->hidden === '') + if($this->config->hidden === '') { throw HoneypotException::forNoHiddenValue(); } - if($this->honeypotConfig->template === '') + if($this->config->template === '') { throw HoneypotException::forNoTemplate(); } - if($this->honeypotConfig->name === '') + if($this->config->name === '') { throw HoneypotException::forNoNameField(); } @@ -100,7 +100,7 @@ function __construct () { */ public function hasContent(RequestInterface $request) { - if($request->getVar($this->honeypotConfig->name)) + if($request->getVar($this->config->name)) { return true; } @@ -114,7 +114,7 @@ public function hasContent(RequestInterface $request) */ public function attachHoneypot(ResponseInterface $response) { - $prep_field = $this->prepareTemplate($this->honeypotConfig->template); + $prep_field = $this->prepareTemplate($this->config->template); $body = $response->getBody(); $body = str_ireplace('', $prep_field, $body); @@ -130,10 +130,10 @@ public function attachHoneypot(ResponseInterface $response) */ protected function prepareTemplate($template): string { - $template = str_ireplace('{label}', $this->honeypotConfig->label, $template); - $template = str_ireplace('{name}', $this->honeypotConfig->name, $template); + $template = str_ireplace('{label}', $this->config->label, $template); + $template = str_ireplace('{name}', $this->config->name, $template); - if($this->honeypotConfig->hidden) + if($this->config->hidden) { $template = '
'. $template . '
'; } diff --git a/tests/system/Honeypot/HoneypotTest.php b/tests/system/Honeypot/HoneypotTest.php index 1efc32f12a67..df134c2da04e 100644 --- a/tests/system/Honeypot/HoneypotTest.php +++ b/tests/system/Honeypot/HoneypotTest.php @@ -16,7 +16,8 @@ public function setUp() parent::setUp(); $this->request = Services::request(); $this->response = Services::response(); - $this->honeypot = new Honeypot(); + $config = new \Config\Honeypot(); + $this->honeypot = new Honeypot($config); } diff --git a/user_guide_src/source/index.rst b/user_guide_src/source/index.rst index 85d6c3d2ec56..4e6c4673b132 100644 --- a/user_guide_src/source/index.rst +++ b/user_guide_src/source/index.rst @@ -61,10 +61,7 @@ General Topics Library Reference ***************** -.. toctree:: - :titlesonly: - - libraries/index +* ``Honeypot`` ****************** Database Reference diff --git a/user_guide_src/source/libraries/honeypot.rst b/user_guide_src/source/libraries/honeypot.rst new file mode 100644 index 000000000000..620bd1b58c29 --- /dev/null +++ b/user_guide_src/source/libraries/honeypot.rst @@ -0,0 +1,87 @@ +===================== +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 done by attaching form fields to any form, +and this form field is hidden from human but accessible to Bot. When data is entered into the field it's +assumed the request is coming from a Bot, then an execption is thrown. + +.. contents:: Page Contents + +Enabling Honeypot +===================== + +To enable Honeypot changes has to be made to the ``Application\Config\Filters.php``. Just uncomment honeypot +from the ``$globals`` Array.:: + + public $globals = [ + 'before' => [ + //'honeypot' + // 'csrf', + ], + 'after' => [ + 'toolbar', + //'honeypot' + ] + ]; + +Customizing Honeypot +===================== + +Honeypot can be customized. It allows the following customization. Customization file can found in `` +Application\Config\Honeypot.php`` and ``.env``. + +* ``Display`` +* ``Label`` +* ``Field Name`` +* ``Template`` + +**Display** + +Display can contain values of ``True`` or ``False``, meaning display the template and hide the template +respectively. The value for display is called ``hidden``.:: + + public $hidden = true; + +The above is for ``Application\Config\Honeypot.php``.:: + + honeypot.hidden = 'true' + +The above is for ``.env`` + +**Label** + +This the label for the input field. The value for label is called ``label``.:: + + public $label = 'Fill This Field'; + +The above is for ``Application\Config\Honeypot.php``.:: + + honeypot.label = 'Fill This Field' + +The above is for ``.env`` + +**Field Name** + +This the field name for the input field. The value for the field name is called ``name``.:: + + public $name = 'honeypot'; + +The above is for ``Application\Config\Honeypot.php``.:: + + honeypot.name = 'honeypot' + +The above is for ``.env`` + +**Template** + +This is the template of the honeypot. The value for the template is called ``template``.:: + + public $template = ''; + +The above is for ``Application\Config\Honeypot.php``.:: + + honeypot.template = '' + +The above is for ``.env`` \ No newline at end of file From faa00892973edb3361fe21ea2caf98fe502fad62 Mon Sep 17 00:00:00 2001 From: Nwoke David Udoka Date: Sat, 9 Jun 2018 12:48:19 +0100 Subject: [PATCH 5/6] Correted typo error Signed-off-by: Nwoke David Udoka --- user_guide_src/source/libraries/honeypot.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/user_guide_src/source/libraries/honeypot.rst b/user_guide_src/source/libraries/honeypot.rst index 620bd1b58c29..36fac6f3f1b4 100644 --- a/user_guide_src/source/libraries/honeypot.rst +++ b/user_guide_src/source/libraries/honeypot.rst @@ -3,7 +3,7 @@ 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 done by attaching form fields to any form, +If it's enabled in ``Application\Config\Filters.php`` file. This is done by attaching form fields to any form, and this form field is hidden from human but accessible to Bot. When data is entered into the field it's assumed the request is coming from a Bot, then an execption is thrown. @@ -29,8 +29,8 @@ from the ``$globals`` Array.:: Customizing Honeypot ===================== -Honeypot can be customized. It allows the following customization. Customization file can found in `` -Application\Config\Honeypot.php`` and ``.env``. +Honeypot can be customized. It allows the following customization. Customization file can found in +``Application\Config\Honeypot.php`` and ``.env``. * ``Display`` * ``Label`` From 33e431bb543b1bc84b0729c75c656021071cfce3 Mon Sep 17 00:00:00 2001 From: Nwoke David Udoka Date: Sat, 9 Jun 2018 12:59:12 +0100 Subject: [PATCH 6/6] Fixed the cached class error. Signed-off-by: Nwoke David Udoka --- application/Config/Services.php | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/application/Config/Services.php b/application/Config/Services.php index b46d657fee60..58a93a0a4e5e 100644 --- a/application/Config/Services.php +++ b/application/Config/Services.php @@ -31,12 +31,18 @@ class Services extends CoreServices // return new \CodeIgniter\Example(); // } - public static function honeypot(BaseConfig $config = null) + public static function honeypot(BaseConfig $config = null, $getShared = true) { + if ($getShared) + { + return self::getSharedInstance('honeypot', $config); + } + if (is_null($config)) { $config = new \Config\Honeypot(); } + return new \CodeIgniter\Honeypot\Honeypot($config); }