1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace luya\web\filters; |
4
|
|
|
|
5
|
|
|
use Yii; |
6
|
|
|
use yii\base\ActionFilter; |
7
|
|
|
use yii\base\InvalidCallException; |
8
|
|
|
use yii\helpers\VarDumper; |
9
|
|
|
use yii\base\Controller; |
10
|
|
|
use luya\helpers\ArrayHelper; |
11
|
|
|
|
12
|
|
|
/** |
13
|
|
|
* Prevent Robots from sending Forms. |
14
|
|
|
* |
15
|
|
|
* This is a very basic spam protection method. If someone sends the form faster then in the |
16
|
|
|
* given {{luya\web\filters\RobotsFilter::$delay}} seconds delay time, an InvalidCallException will be thrown. |
17
|
|
|
* |
18
|
|
|
* Usage: |
19
|
|
|
* |
20
|
|
|
* ```php |
21
|
|
|
* public function behaviors() |
22
|
|
|
* { |
23
|
|
|
* return [ |
24
|
|
|
* 'robotsFilter' => RobotsFilter::class |
25
|
|
|
* ]; |
26
|
|
|
* } |
27
|
|
|
* ``` |
28
|
|
|
* |
29
|
|
|
* In order to configure the capture delay time use: |
30
|
|
|
* |
31
|
|
|
* ```php |
32
|
|
|
* public function behaviors() |
33
|
|
|
* { |
34
|
|
|
* return [ |
35
|
|
|
* 'robotsFilter' => [ |
36
|
|
|
* 'class' => RobotsFilter::class, |
37
|
|
|
* 'delay' => 0.5, |
38
|
|
|
* ] |
39
|
|
|
* ]; |
40
|
|
|
* } |
41
|
|
|
* ``` |
42
|
|
|
* |
43
|
|
|
* @author Basil Suter <[email protected]> |
44
|
|
|
* @since 1.0.0 |
45
|
|
|
*/ |
46
|
|
|
class RobotsFilter extends ActionFilter |
47
|
|
|
{ |
48
|
|
|
/** |
49
|
|
|
* @var float The number of seconds a human would have to fill up the form, before the form is triggered as invalid. |
50
|
|
|
*/ |
51
|
|
|
public $delay = 2.5; |
52
|
|
|
|
53
|
|
|
/** |
54
|
|
|
* @var string|null A string which identifiers the current robots filter in case you have multiple controllers on the same page with robot filters enabled. |
55
|
|
|
* @since 1.0.17 |
56
|
|
|
*/ |
57
|
|
|
public $sessionKey; |
58
|
|
|
|
59
|
|
|
const ROBOTS_FILTER_SESSION_IDENTIFIER = '__robotsFilterRenderTime'; |
60
|
|
|
|
61
|
|
|
/** |
62
|
|
|
* @return integer Returns the latest render timestamp. |
63
|
|
|
*/ |
64
|
|
|
protected function getRenderTime() |
65
|
|
|
{ |
66
|
|
|
$value = Yii::$app->session->get(self::ROBOTS_FILTER_SESSION_IDENTIFIER, time()); |
67
|
|
|
|
68
|
|
|
if (isset($value[$this->getSessionKeyByOwner()])) { |
69
|
|
|
return $value[$this->getSessionKeyByOwner]; |
|
|
|
|
70
|
|
|
} |
71
|
|
|
|
72
|
|
|
return $value; |
73
|
|
|
} |
74
|
|
|
|
75
|
|
|
/** |
76
|
|
|
* Render Time Setter. |
77
|
|
|
* |
78
|
|
|
* @param integer $time Set the last action timestamp. |
79
|
|
|
*/ |
80
|
|
|
protected function setRenderTime($time) |
81
|
|
|
{ |
82
|
|
|
Yii::$app->session->set(self::ROBOTS_FILTER_SESSION_IDENTIFIER, [$this->getSessionKeyByOwner() => $time]); |
83
|
|
|
} |
84
|
|
|
|
85
|
|
|
/** |
86
|
|
|
* Get a specific key for the current robots filter session array. |
87
|
|
|
* |
88
|
|
|
* This ensures that when multiple forms are on the same page, only the robot check is handeld for the given module name. |
89
|
|
|
* |
90
|
|
|
* @return string |
91
|
|
|
* @since 1.0.17 |
92
|
|
|
*/ |
93
|
|
|
protected function getSessionKeyByOwner() |
94
|
|
|
{ |
95
|
|
|
if ($this->sessionKey) { |
|
|
|
|
96
|
|
|
return $this->sessionKey; |
97
|
|
|
} |
98
|
|
|
|
99
|
|
|
if ($this->owner instanceof Controller) { |
100
|
|
|
return $this->owner->module->id; |
101
|
|
|
} |
102
|
|
|
|
103
|
|
|
return 'generic'; |
104
|
|
|
} |
105
|
|
|
|
106
|
|
|
/** |
107
|
|
|
* Return the elapsed process time to fill in the form. |
108
|
|
|
* |
109
|
|
|
* @return number The elapsed time in seconds. |
110
|
|
|
*/ |
111
|
|
|
protected function getElapsedProcessTime() |
112
|
|
|
{ |
113
|
|
|
return (int) (time() - $this->getRenderTime()); |
114
|
|
|
} |
115
|
|
|
|
116
|
|
|
/** |
117
|
|
|
* @inheritdoc |
118
|
|
|
*/ |
119
|
|
|
public function beforeAction($action) |
120
|
|
|
{ |
121
|
|
|
if (Yii::$app->request->isPost) { |
122
|
|
|
if ($this->getElapsedProcessTime() < $this->delay) { |
123
|
|
|
throw new InvalidCallException("Robots Filter has detected an invalid Request: " . VarDumper::export(ArrayHelper::coverSensitiveValues(Yii::$app->request->post()))); |
124
|
|
|
} |
125
|
|
|
} |
126
|
|
|
|
127
|
|
|
return true; |
128
|
|
|
} |
129
|
|
|
|
130
|
|
|
/** |
131
|
|
|
* @inheritdoc |
132
|
|
|
*/ |
133
|
|
|
public function afterAction($action, $result) |
134
|
|
|
{ |
135
|
|
|
$this->setRenderTime(time()); |
136
|
|
|
|
137
|
|
|
return $result; |
138
|
|
|
} |
139
|
|
|
} |
140
|
|
|
|
An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.
If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.