Completed
Push — master ( 1b2d0a...89ea1a )
by Oscar
03:00
created

FormTimestamp::getGenerator()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 1
Metric Value
c 1
b 0
f 1
dl 0
loc 4
rs 10
cc 1
eloc 2
nc 1
nop 1
1
<?php
2
3
namespace Psr7Middlewares\Middleware;
4
5
use Psr7Middlewares\Middleware;
6
use Psr7Middlewares\Utils;
7
use Psr\Http\Message\ServerRequestInterface;
8
use Psr\Http\Message\ResponseInterface;
9
use RuntimeException;
10
use Exception;
11
12
/**
13
 * Middleware to span protection using the timestamp value in forms.
14
 */
15
class FormTimestamp
16
{
17
    use Utils\FormTrait;
18
    use Utils\CryptTrait;
19
20
    const KEY_GENERATOR = 'FORM_TIMESTAMP_GENERATOR';
21
22
    /**
23
     * @var string The honeypot input name
24
     */
25
    private $inputName = 'hpt_time';
26
27
    /**
28
     * @var int Minimum seconds to determine whether the request is a bot
29
     */
30
    private $min = 3;
31
32
    /**
33
     * @var int Max seconds to expire the form. Zero to do not expire
34
     */
35
    private $max = 0;
36
37
    /**
38
     * Returns a callable to generate the inputs.
39
     *
40
     * @param ServerRequestInterface $request
41
     *
42
     * @return callable|null
43
     */
44
    public static function getGenerator(ServerRequestInterface $request)
45
    {
46
        return Middleware::getAttribute($request, self::KEY_GENERATOR);
47
    }
48
49
    /**
50
     * Set the field name.
51
     * 
52
     * @param string $inputName
53
     * 
54
     * @return self
55
     */
56
    public function inputName($inputName)
57
    {
58
        $this->inputName = $inputName;
59
60
        return $this;
61
    }
62
63
    /**
64
     * Minimum time required.
65
     * 
66
     * @param int $seconds
67
     * 
68
     * @return self
69
     */
70
    public function min($seconds)
71
    {
72
        $this->min = $seconds;
73
74
        return $this;
75
    }
76
77
    /**
78
     * Max time before expire the form.
79
     * 
80
     * @param int $seconds
81
     * 
82
     * @return self
83
     */
84
    public function max($seconds)
85
    {
86
        $this->max = $seconds;
87
88
        return $this;
89
    }
90
91
    /**
92
     * Execute the middleware.
93
     *
94
     * @param ServerRequestInterface $request
95
     * @param ResponseInterface      $response
96
     * @param callable               $next
97
     *
98
     * @return ResponseInterface
99
     */
100
    public function __invoke(ServerRequestInterface $request, ResponseInterface $response, callable $next)
101
    {
102
        if (!Middleware::hasAttribute($request, FormatNegotiator::KEY)) {
103
            throw new RuntimeException('FormTimestamp middleware needs FormatNegotiator executed before');
104
        }
105
106
        if (FormatNegotiator::getFormat($request) !== 'html') {
107
            return $next($request, $response);
108
        }
109
110
        if (Utils\Helpers::isPost($request) && !$this->isValid($request)) {
111
            return $response->withStatus(403);
112
        }
113
114
        $value = $this->encrypt(time());
115
116
        $generator = function () use ($value) {
117
            return '<input type="hidden" name="'.$this->inputName.'" value="'.$value.'">';
118
        };
119
120
        $response = $next($request, $response);
121
122
        return $this->insertIntoPostForms($response, function ($match) use ($generator) {
123
            return $match[0].$generator();
124
        });
125
    }
126
127
    /**
128
     * Check whether the request is valid.
129
     * 
130
     * @param ServerRequestInterface $request
131
     * 
132
     * @return bool
133
     */
134
    private function isValid(ServerRequestInterface $request)
135
    {
136
        $data = $request->getParsedBody();
137
138
        //value does not exists
139
        if (empty($data[$this->inputName])) {
140
            return false;
141
        }
142
143
        try {
144
            $time = $this->decrypt($data[$this->inputName]);
145
        } catch (Exception $e) {
146
            return false;
147
        }
148
149
        //value is not valid
150
        if (!is_numeric($time)) {
151
            return false;
152
        }
153
154
        $now = time();
155
156
        //sent from future
157
        if ($now < $time) {
158
            return false;
159
        }
160
161
        $diff = $now - $time;
162
163
        //check min
164
        if ($diff < $this->min) {
165
            return false;
166
        }
167
168
        //check max
169
        if ($this->max && $diff > $this->max) {
170
            return false;
171
        }
172
173
        return true;
174
    }
175
}
176