Add honeypot field for spambot

[MAILPOET-1014]
This commit is contained in:
Pavel Dohnal
2017-08-15 14:33:45 +02:00
parent f7b1016e63
commit 18f208cf47
5 changed files with 47 additions and 13 deletions

View File

@ -66,6 +66,11 @@ class Subscribers extends APIEndpoint {
APIError::BAD_REQUEST => __('Please specify a valid form ID.', 'mailpoet') APIError::BAD_REQUEST => __('Please specify a valid form ID.', 'mailpoet')
)); ));
} }
if(!empty($data['email'])) {
return $this->badRequest(array(
APIError::BAD_REQUEST => __('Please leave the first field empty.', 'mailpoet')
));
}
$segment_ids = (!empty($data['segments']) $segment_ids = (!empty($data['segments'])
? (array)$data['segments'] ? (array)$data['segments']
@ -74,6 +79,8 @@ class Subscribers extends APIEndpoint {
$segment_ids = $form->filterSegments($segment_ids); $segment_ids = $form->filterSegments($segment_ids);
unset($data['segments']); unset($data['segments']);
$data = $this->deobfuscateFormPayload($data);
if(empty($segment_ids)) { if(empty($segment_ids)) {
return $this->badRequest(array( return $this->badRequest(array(
APIError::BAD_REQUEST => __('Please select a list.', 'mailpoet') APIError::BAD_REQUEST => __('Please select a list.', 'mailpoet')
@ -115,6 +122,18 @@ class Subscribers extends APIEndpoint {
} }
} }
private function deobfuscateFormPayload($data) {
$result = array();
foreach($data as $key => $value) {
if(strpos($key, 'form_field_') === 0) {
$result[base64_decode(substr($key, 11))] = $value;
} else {
$result[$key] = $value;
}
}
return $result;
}
function save($data = array()) { function save($data = array()) {
if(empty($data['segments'])) { if(empty($data['segments'])) {
$data['segments'] = array(); $data['segments'] = array();

View File

@ -104,7 +104,7 @@ abstract class Base {
if((int)$block['id'] > 0) { if((int)$block['id'] > 0) {
return 'cf_'.$block['id']; return 'cf_'.$block['id'];
} else { } else {
return $block['id']; return 'form_field_'.base64_encode($block['id']);//obfuscate field name for spambots
} }
} }

View File

@ -15,6 +15,7 @@ class Renderer {
$styles = new Util\Styles(static::getStyles($form)); $styles = new Util\Styles(static::getStyles($form));
$html = '<style type="text/css">'; $html = '<style type="text/css">';
$html .= '.mailpoet_hp_email_label{position: absolute;left: -999em;}';// move honeypot field out of the sight
$html .= $styles->render($prefix); $html .= $styles->render($prefix);
$html .= '</style>'; $html .= '</style>';
@ -38,7 +39,8 @@ class Renderer {
} }
static function renderBlocks($blocks = array()) { static function renderBlocks($blocks = array()) {
$html = ''; // this is a honeypot for spambots
$html = '<label class="mailpoet_hp_email_label">Please leave this field empty<input type="email" name="data[email]"></label>';
foreach($blocks as $key => $block) { foreach($blocks as $key => $block) {
$html .= static::renderBlock($block)."\n"; $html .= static::renderBlock($block)."\n";
} }

View File

@ -390,10 +390,22 @@ class SubscribersTest extends \MailPoetTest {
expect($response->errors[0]['message'])->contains('has no method'); expect($response->errors[0]['message'])->contains('has no method');
} }
function testItFailsWithEmailFilled() {
$router = new Subscribers();
$response = $router->subscribe(array(
'form_id' => $this->form->id,
'email' => 'toto@mailpoet.com'
// no form ID specified
));
expect($response->status)->equals(APIResponse::STATUS_BAD_REQUEST);
expect($response->errors[0]['message'])->equals('Please leave the first field empty.');
}
function testItCannotSubscribeWithoutFormID() { function testItCannotSubscribeWithoutFormID() {
$router = new Subscribers(); $router = new Subscribers();
$response = $router->subscribe(array( $response = $router->subscribe(array(
'email' => 'toto@mailpoet.com' 'form_field_ZW1haWw' => 'toto@mailpoet.com'
// no form ID specified // no form ID specified
)); ));
@ -404,7 +416,7 @@ class SubscribersTest extends \MailPoetTest {
function testItCannotSubscribeWithoutSegmentsIfTheyAreSelectedByUser() { function testItCannotSubscribeWithoutSegmentsIfTheyAreSelectedByUser() {
$router = new Subscribers(); $router = new Subscribers();
$response = $router->subscribe(array( $response = $router->subscribe(array(
'email' => 'toto@mailpoet.com', 'form_field_ZW1haWw=' => 'toto@mailpoet.com',
'form_id' => $this->form->id 'form_id' => $this->form->id
// no segments specified // no segments specified
)); ));
@ -416,7 +428,7 @@ class SubscribersTest extends \MailPoetTest {
function testItCanSubscribe() { function testItCanSubscribe() {
$router = new Subscribers(); $router = new Subscribers();
$response = $router->subscribe(array( $response = $router->subscribe(array(
'email' => 'toto@mailpoet.com', 'form_field_ZW1haWw=' => 'toto@mailpoet.com',
'form_id' => $this->form->id, 'form_id' => $this->form->id,
'segments' => array($this->segment_1->id, $this->segment_2->id) 'segments' => array($this->segment_1->id, $this->segment_2->id)
)); ));
@ -431,7 +443,7 @@ class SubscribersTest extends \MailPoetTest {
$router = new Subscribers(); $router = new Subscribers();
$response = $router->subscribe(array( $response = $router->subscribe(array(
'email' => 'toto@mailpoet.com', 'form_field_ZW1haWw=' => 'toto@mailpoet.com',
'form_id' => $this->form->id 'form_id' => $this->form->id
// no segments specified // no segments specified
)); ));
@ -453,7 +465,7 @@ class SubscribersTest extends \MailPoetTest {
$router = new Subscribers(); $router = new Subscribers();
$response = $router->subscribe(array( $response = $router->subscribe(array(
'email' => 'toto@mailpoet.com', 'form_field_ZW1haWw=' => 'toto@mailpoet.com',
'form_id' => $this->form->id, 'form_id' => $this->form->id,
'segments' => array($this->segment_1->id, $this->segment_2->id) 'segments' => array($this->segment_1->id, $this->segment_2->id)
)); ));
@ -465,7 +477,7 @@ class SubscribersTest extends \MailPoetTest {
function testItCanFilterOutNonFormFieldsWhenSubscribing() { function testItCanFilterOutNonFormFieldsWhenSubscribing() {
$router = new Subscribers(); $router = new Subscribers();
$response = $router->subscribe(array( $response = $router->subscribe(array(
'email' => 'toto@mailpoet.com', 'form_field_ZW1haWw=' => 'toto@mailpoet.com',
'form_id' => $this->form->id, 'form_id' => $this->form->id,
'segments' => array($this->segment_1->id, $this->segment_2->id), 'segments' => array($this->segment_1->id, $this->segment_2->id),
// exists in table and in the form // exists in table and in the form
@ -486,14 +498,14 @@ class SubscribersTest extends \MailPoetTest {
$router = new Subscribers(); $router = new Subscribers();
$response = $router->subscribe(array( $response = $router->subscribe(array(
'email' => 'toto@mailpoet.com', 'form_field_ZW1haWw=' => 'toto@mailpoet.com',
'form_id' => $this->form->id, 'form_id' => $this->form->id,
'segments' => array($this->segment_1->id, $this->segment_2->id) 'segments' => array($this->segment_1->id, $this->segment_2->id)
)); ));
try { try {
$response = $router->subscribe(array( $response = $router->subscribe(array(
'email' => 'tata@mailpoet.com', 'form_field_ZW1haWw=' => 'tata@mailpoet.com',
'form_id' => $this->form->id, 'form_id' => $this->form->id,
'segments' => array($this->segment_1->id, $this->segment_2->id) 'segments' => array($this->segment_1->id, $this->segment_2->id)
)); ));

View File

@ -11,6 +11,7 @@ use MailPoet\Util\Security;
class FormTest extends \MailPoetTest { class FormTest extends \MailPoetTest {
function _before() { function _before() {
$this->testEmail = 'test@example.com';
$this->segment = SegmentModel::createOrUpdate( $this->segment = SegmentModel::createOrUpdate(
array( array(
'name' => 'Test segment' 'name' => 'Test segment'
@ -34,7 +35,7 @@ class FormTest extends \MailPoetTest {
'action' => 'mailpoet_subscription_form', 'action' => 'mailpoet_subscription_form',
'data' => array( 'data' => array(
'form_id' => $this->form->id, 'form_id' => $this->form->id,
'email' => 'test@example.com' 'form_field_ZW1haWw=' => $this->testEmail
), ),
'token' => Security::generateToken(), 'token' => Security::generateToken(),
'api_version' => 'v1', 'api_version' => 'v1',
@ -58,7 +59,7 @@ class FormTest extends \MailPoetTest {
} }
]); ]);
$result = Form::onSubmit($this->request_data); $result = Form::onSubmit($this->request_data);
expect(SubscriberModel::findOne($this->request_data['data']['email']))->notEmpty(); expect(SubscriberModel::findOne($this->testEmail))->notEmpty();
$mock->verifyInvoked('redirectBack'); $mock->verifyInvoked('redirectBack');
expect($result['mailpoet_success'])->equals($this->form->id); expect($result['mailpoet_success'])->equals($this->form->id);
expect($result['mailpoet_error'])->null(); expect($result['mailpoet_error'])->null();
@ -78,7 +79,7 @@ class FormTest extends \MailPoetTest {
} }
]); ]);
$result = Form::onSubmit($this->request_data); $result = Form::onSubmit($this->request_data);
expect(SubscriberModel::findOne($this->request_data['data']['email']))->notEmpty(); expect(SubscriberModel::findOne($this->testEmail))->notEmpty();
$mock->verifyInvoked('redirectTo'); $mock->verifyInvoked('redirectTo');
expect($result)->regExp('/http.*?sample-post/i'); expect($result)->regExp('/http.*?sample-post/i');
} }