Skip to content

Commit

Permalink
[feat] Antispam forms (#609)
Browse files Browse the repository at this point in the history
* Add FormHoneypot DB model

* Add form honeypot in templates

* Check for form honeypots in contact controller

* Add timestamp to honeypot catches

* Add datetime in model
  • Loading branch information
subiabre authored Jun 21, 2024
1 parent c80e85c commit 2a35f69
Show file tree
Hide file tree
Showing 5 changed files with 171 additions and 5 deletions.
1 change: 1 addition & 0 deletions Resources/templates/default/about/contact.php
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@
<div class="field">
<label for="email"><?= $this->text('contact-email-field') ?></label><br />
<input class="short" type="text" id="email" name="email" value="<?= $this->data['email'] ?>"/>
<?= $this->insert($this->honeypot->template, $this->honeypot->params) ?>
</div>
</td>
</tr>
Expand Down
5 changes: 5 additions & 0 deletions Resources/templates/default/partials/form/honeypot.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<label for="<?= $this->trap ?>" style="width: 0px; height: 0px; margin: 0px; padding: 0px; opacity: 0; display: block;">
<?= $this->text('contact-email-field') ?>
</label>
<br style="width: 0px; height: 0px; margin: 0px; padding: 0px; opacity: 0; display: block;" />
<input id="<?= $this->trap ?>" name="<?= $this->trap ?>" value="" type="text" class="short" style="width: 0px; height: 0px; margin: 0px; padding: 0px; opacity: 0; display: block;" />
56 changes: 56 additions & 0 deletions db/migrations/20240620092323_goteo_form_honeypot.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
<?php

/**
* Migration Task class.
*/
class GoteoFormHoneypot
{
public function preUp()
{
// add the pre-migration code here
}

public function postUp()
{
// add the post-migration code here
}

public function preDown()
{
// add the pre-migration code here
}

public function postDown()
{
// add the post-migration code here
}

/**
* Return the SQL statements for the Up migration
*
* @return string The SQL string to execute for the Up migration.
*/
public function getUpSQL()
{
return "
CREATE TABLE `form_honeypot` (
`id` BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
`trap` VARCHAR(50) NOT NULL,
`prey` TEXT,
`template` TEXT,
`datetime` TIMESTAMP,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
";
}

/**
* Return the SQL statements for the Down migration
*
* @return string The SQL string to execute for the Down migration.
*/
public function getDownSQL()
{
return "DROP TABLE `form_honeypot`";
}
}
33 changes: 28 additions & 5 deletions src/Goteo/Controller/ContactController.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
use Goteo\Core\Controller;
use Goteo\Library;
use Goteo\Library\Text;
use Goteo\Model\FormHoneypot;
use Goteo\Model\Mail;
use Goteo\Model\Page;
use Goteo\Model\Template;
Expand Down Expand Up @@ -84,6 +85,22 @@ public function indexAction (Request $request) {
}
}

// check honeypot trap
$trap = Session::get('form-honeypot');
Session::del('form-honeypot');
if (FormHoneypot::checkTrap($trap, $request)) {
$honeypot = new FormHoneypot;
$honeypot->trap = $trap;
$honeypot->prey = $request->request->get($trap);

$honeypot->validate($honeypotErrors);
$honeypot->save($honeypotErrors);

// Make robot makers think they have succeeded
Message::info('Mensaje de contacto enviado correctamente.');
return $this->redirect('/contact');
}

$data = array(
'tag' => $tag,
'subject' => $subject,
Expand All @@ -104,19 +121,19 @@ public function indexAction (Request $request) {
$user_template=Template::CONTACT_AUTO_REPLY_NEW_PROJECT;
break;
case 'contact-form-project-form-tag-name':
$to_admin = Config::get('mail.contact');
$user_template=Template::CONTACT_AUTO_REPLY_PROJECT_FORM;
$to_admin = Config::get('mail.contact');
$user_template=Template::CONTACT_AUTO_REPLY_PROJECT_FORM;
break;
case 'contact-form-dev-tag-name':
$to_admin = Config::get('mail.fail');
$user_template=Template::CONTACT_AUTO_REPLY_DEV;
$to_admin = Config::get('mail.fail');
$user_template=Template::CONTACT_AUTO_REPLY_DEV;
break;
case 'contact-form-relief-tag-name':
$to_admin = Config::get('mail.donor');
$user_template=Template::CONTACT_AUTO_REPLY_RELIEF;
break;
case 'contact-form-service-tag-name':
$to_admin = Config::get('mail.management');
$to_admin = Config::get('mail.management');
break;
default:
$to_admin = Config::get('mail.contact');
Expand Down Expand Up @@ -170,17 +187,23 @@ public function indexAction (Request $request) {
$captcha->build();
Session::store('captcha-phrase', $captcha->getPhrase());
}

// Generate a new form token
$token = sha1(uniqid(mt_rand(), true));
Session::store('form-token', $token);

// Generate honeypot fields
$honeypot = FormHoneypot::layTrap();
Session::store('form-honeypot', $honeypot->trap);

return $this->viewResponse('about/contact',
array(
'data' => $data,
'tags' => $tags,
'token' => $token,
'page' => Page::get('contact'),
'captcha' => $captcha,
'honeypot' => $honeypot,
'errors' => $errors
)
);
Expand Down
81 changes: 81 additions & 0 deletions src/Goteo/Model/FormHoneypot.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
<?php

namespace Goteo\Model;

use Goteo\Core\Model;
use Symfony\Component\HttpFoundation\Request;

class FormHoneypot extends Model
{
public $id;

/**
* This value should be left blank by humans but non-blank by robots
*/
public $trap;

/**
* This value was, most-likely, introduced by a robot
*/
public $prey;

public $params;

/**
* The template for the trap field
*/
public $template = 'partials/form/honeypot';

/**
* The date at wich the trap was laid
*/
public $datetime;

protected $Table = 'form_honeypot';
static protected $Table_static = 'form_honeypot';

public function save(&$errors = array())
{
if (!$this->validate($errors)) return false;

$this->dbInsertUpdate(['id', 'trap', 'prey', 'template', 'datetime']);
}

public function validate(&$errors = array())
{
if (empty($errors))
return true;
else
return false;
}

/**
* Get a trapped form field that is invisible to humans and juicy for robots to fill
*/
public static function layTrap()
{
$honeypot = new FormHoneypot;
$honeypot->trap = "email_addr_confirm";
$honeypot->prey = "";
$honeypot->datetime = new \DateTime();
$honeypot->params = [
'trap' => $honeypot->trap,
'prey' => $honeypot->prey
];

return $honeypot;
}

/**
* Checks if something got caught in the trap
* @return bool `true` if caught something, `false` if not
*/
public static function checkTrap(string $trap, $data): bool
{
if ($data instanceof Request) {
return $data->request->get($trap) !== "";
}

return false;
}
}

0 comments on commit 2a35f69

Please sign in to comment.