Source of file HoneypotForm.php
Size: 4,991 Bytes - Last Modified: 2021-12-23T10:02:06+00:00
/var/www/docs.ssmods.com/process/src/code/forms/HoneypotForm.php
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176 | <?php /** * Honeypot form, auto-generates a honeypot field with random name * and ensures it's correctly empty on submission. * This check is provided via a separate function so that it doesn't * interrupt the validation system, which is designed to show a message * to the user; exactly what we don't want in this case. */ class HoneypotForm extends Form { /** * The minimum amount of time (seconds) to fill in the form. * We assume that a quicker formfill than this is a bot. * @var integer */ public static $minimum_formfill_seconds = 10; /** * If this is false, then timestamp fields won't be added or checked * @var boolean */ public static $use_timestamps = false; /** * The hash used as a token for this form. * @var string */ protected $honeypot = ''; /** * Use this to prevent generation of a random field name * each time. * @see set_force_token * @var boolean */ protected static $force_token = false; /** * Randomized value used as a css classname to * keep the honeypot element hidden from users. * @var string */ protected static $css_class = ''; /** * Fetch this token from the session * @return string Honeypot hash */ protected function getToken() { $this->honeypot = Session::get('HoneypotForm.' . $this->Name . '.Honeypot'); return $this->honeypot; } public function getHoneypotFieldName() { if (!$this->getToken()) { $this->setToken(); } return $this->getToken(); } /** * Set the token (more correctly generate). * @uses Session */ protected function setToken() { $token = self::$force_token; if ($token === false) { $generator = new RandomGenerator(); $token = 'hp_' . $generator->randomToken('sha1'); } $this->honeypot = $token; Session::set('HoneypotForm.' . $this->Name . '.Honeypot', $this->honeypot); return $this->honeypot; } /** * Generate the name of the timestamp field * @return string */ protected function getTimeFieldName() { return md5($this->honeypot . 'timestamp'); } /** * Create a new Honeypot form, that is a form with a honeypot field; if the * honeypot field is filled in, the form submission will silently fail. * This is designed to catch spambots/etc, as users should not fill it in (both * labeled as such, and hidden via CSS). * * @param Controller $controller @see Form::__construct * @param string $name @see Form::__construct * @param FieldSet $fields @see Form::__construct * @param FieldSet $actions @see Form::__construct * @param Validator $validator @see Form::__construct */ public function __construct($controller, $name, FieldList $fields=null, FieldList $actions=null, $validator=null) { $this->Name = $name; if (!$this->getToken()) { $this->setToken(); } $field = new TextField($this->honeypot, 'Please do not fill in this field'); $field->addExtraClass(self::$css_class); $fields->push($field); if (self::$use_timestamps) { $timeField = new HiddenField($this->getTimeFieldName()); $timeField->setValue(time()); $fields->push($timeField); } parent::__construct($controller, $name, $fields, $actions, $validator); } /** * Check whether the honeypot field was (thus incorrectly) filled in. * @param array $data Form submit data * @return boolean true if field was left empty as desired. */ public function validateHoneypot($data) { $fieldName = $this->getToken(); $timestampField = $this->getTimeFieldName(); $now = time(); $then = isset($data[$timestampField]) ? $data[$timestampField] : $now; $notTooFast = (($now - $then) > self::$minimum_formfill_seconds); if (!self::$use_timestamps) { $notTooFast = true; } if (isset($data[$fieldName]) && empty($data[$fieldName]) && $notTooFast) { return true; } // If we get here, then the invisible Honeypot field has been filled in, lets assume by a bot. // Log it and drop it silently. SS_Log::log(new Exception('Possible bot attack in ' . $this->Name . ' from IP ' . $_SERVER['REMOTE_ADDR']), SS_Log::WARN); return false; } /** * Render the CSS for the honeypot field so that the field is hidden, without * using a predictable classname or an inline 'display:none'. * Doesn't stop a clever bot with javascript! * * @uses Requirements::customCSS to render the css to the page */ public static function render_css() { $cssClass = Session::get('HoneypotForm.CSSClass'); if (!$cssClass) { $generator = new RandomGenerator(); $cssClass = 'hp_' . $generator->randomToken('sha1'); Session::set('HoneypotForm.CSSClass', $cssClass); } self::$css_class = $cssClass; Requirements::customCSS(<<<CSS .{$cssClass} { display: none; } CSS ); } public static function set_force_token($token) { $oldToken = self::$force_token; if (!$token) { self::$force_token = false; } else { self::$force_token = $token; } return $oldToken; } } |