Completed
Push — master ( 42ee59...5b613b )
by Basil
05:00
created

RobotsFilter::getSessionKeyByOwner()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 12
rs 9.8666
c 0
b 0
f 0
cc 3
nc 3
nop 0
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];
0 ignored issues
show
Bug introduced by
The property getSessionKeyByOwner does not seem to exist. Did you mean sessionKey?

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.

Loading history...
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) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->sessionKey of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
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