Best practice – Jak na spam z formulářů

Upozornění: Tohle vlákno je hodně staré a informace nemusí být platné pro současné Nette.
Ondřej Mirtes
Člen | 1536
+
0
-

Ahoj,
již od úsvitu času používám ochranu formulářů proti spamu podle Jakuba Vrány a zatím mě nezklamala. Ve zkratce jde o to, že k formuláři připojíte textové pole, do kterého má uživatel vyplnit předem daný řetězec (který se ani nemusí měnit) a v duchu progressive enhancement ho automaticky vyplníte a skryjete javascriptem. Sází to na to, že boti javascript (a jQuery a načítání skriptů z externích souborů) neumí, což možná jednou přestane platit, ale zatím má tato metoda, co vím, 100% účinnost. A není otravná jako Captcha.

Jak na to v Nette? Vytvoříme si potomka AppForm pojmenovaného BaseForm a toho budeme od této chvíle všude používat. Abychom nezapomněli někde formulářovou ochranu přidat, překryjeme metodu addSubmit z FormContainer, takže k přidání dodatečného textového pole dojde při přidávání potvrzovacího tlačítka.

Volitelně ještě můžete pomocí addProtection přidat ochranu proti CSRF a v celé aplikaci tak budete mít automaticky skutečně neprůstřelné formuláře :)

Pro vyplnění a skrytí formuláře použijeme jQuery. Aby bylo řešení unobtrusive, přiřadíme labelu a controlu CSS třídu a navěšení javascriptu provedeme přes ní.

<?php

/* namespace Classes;

use Nette\Application\AppForm;
use Nette\Forms\Form; /**/

class BaseForm extends AppForm {

	public function addSubmit($name, $caption) {
		$this->addProtection('Form has expired.');

		$noSpam = $this->addText('nospam', 'Fill in „nospam“')
				->addRule(Form::FILLED, 'You are a spambot!')
           			->addRule(Form::EQUAL, 'You are a spambot!', 'nospam');

        	$noSpam->getLabelPrototype()->class('nospam');
        	$noSpam->getControlPrototype()->class('nospam');

        	return parent::addSubmit($name, $caption);
	}

}

Následující javascript doporučuji mít v externím souboru spolu s ostatními skripty (zajaxování odkazů apod.):

$(function() {
	$(".nospam").hide();
	$("input.nospam").val("no" + "spam");
});

EDIT: 31.8.2009 – malý update. Při zavolání setDefaults na Form se zavolá isSubmitted() a to zavolá processHttpRequest(), tudíž pak už nelze přidávat další formulářová pole (do formuláře na vykreslení se přidají, ale zpracovávající PHP skript je už nevidí). Nyní při použití BaseForm vyžaduji zavolat setDefaults až po přidání submitovacího tlačítka.

Párkrát jsem se spálil, kdy mi můj formulář hlásil útok spambota, přitom vše jsem měl v pořádku, až na to předčasné volání setDefaults.

EDIT: 14.9.2009 – po dnešním updatu formulářů již není kontrola volání setDefaults potřeba, vracím tedy kód třídy do původního stavu.

Editoval LastHunter (14. 9. 2009 19:12)

ic
Člen | 430
+
0
-

Taky mám něco podobného, akorát bez jQuery, formulář vykresluju manuálně a pod ním je javascript a noscript (prostě jako v příkladu).
Není to tak elegantní, ale při pomalém načítání stránky neproblikává mizající input.

upravit
Člen | 2
+
0
-

Supr, akorat bych dodal, ze je treba tento formular zpracovat metodou onSubmit. Nefunguje mi totiz:

<?php
$form->addSubmit('ok', 'Odeslat')->onClick[] = array($this, 'eventFormVzkazClick');
?>

ale tohlenc uz jo:

<?php
$form->onSubmit[] = array($this, 'handleFormSubmitted');
?>
jasir
Člen | 746
+
0
-

To by mělo jít snando napravit fixnutím metody addSubmit() z prvního příspěvku změnou

parent::addSubmit($name, $caption);

na

return parent::addSubmit($name, $caption);
Honza Kuchař
Člen | 1662
+
0
-

Nešlo by to předělat do stylu

$form->addProtection();

?

Ondřej Mirtes
Člen | 1536
+
0
-

honzakuchar napsal(a):

Nešlo by to předělat do stylu

$form->addProtection();

?

Určitě, akorát místo addSubmit překryješ metodu addProtection. Já to ale chtěl mít všude a submitovací tlačítko mám taky všude, proto jsem to dal do něj.

laada
Člen | 35
+
0
-

LastHunter napsal(a):

honzakuchar napsal(a):

Nešlo by to předělat do stylu

$form->addProtection();

?

Určitě, akorát místo addSubmit překryješ metodu addProtection. Já to ale chtěl mít všude a submitovací tlačítko mám taky všude, proto jsem to dal do něj.

Hezke.

Mit to v metode addProtection() mi pripada kapku systemovejsi. Docela casto se taky pouziva odesilani formulare po udalosti onChange na selectboxu.

Editoval laada (16. 9. 2009 20:29)

Michalek
Člen | 211
+
0
-

V případě okamžitého ukládání do databáze z getValues() je ale potřeba udělat unset($form[‚nospam‘]); že? Já jestli nedělám něco blbě :-)

DocX
Člen | 154
+
0
-

Pěkná práce.

Jen chci ale trochu komentnout to úvodní slovo

… Sází to na to, že boti javascript (a jQuery a načítání skriptů z externích souborů) neumí, což možná jednou přestane platit, ale zatím má tato metoda, co vím, 100% účinnost. A není otravná jako Captcha…

Na nějaké boty, slídící po netu a zkoušející automaticky odeslat jakýkoliv formulář to samozřejmě platí na 100%. Ale pokud si tvoji stránku vyčíhne nějaký zlotřilec a bude tě chtít zahltit spamem, vytvořit milion účtů, apod., tak to jistě dokáže jednoduše odpozorovat. Tam je captcha stále jediná možnost :)

Ondřej Mirtes
Člen | 1536
+
0
-

DocX napsal(a):

Pěkná práce.

Jen chci ale trochu komentnout to úvodní slovo

… Sází to na to, že boti javascript (a jQuery a načítání skriptů z externích souborů) neumí, což možná jednou přestane platit, ale zatím má tato metoda, co vím, 100% účinnost. A není otravná jako Captcha…

Na nějaké boty, slídící po netu a zkoušející automaticky odeslat jakýkoliv formulář to samozřejmě platí na 100%. Ale pokud si tvoji stránku vyčíhne nějaký zlotřilec a bude tě chtít zahltit spamem, vytvořit milion účtů, apod., tak to jistě dokáže jednoduše odpozorovat. Tam je captcha stále jediná možnost :)

Tak jasně, ale to už se dá řešit individuálně a třeba nějakým složitějším generováním, co se má zadat do toho pole, ale to jsem zatím nepotřeboval, nikdo to neprolomil :)