Completed
Push — master ( df4068...6a3bd7 )
by Joschi
03:02
created

Antibot::armorInputs()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 16

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 3

Importance

Changes 0
Metric Value
dl 0
loc 16
ccs 8
cts 8
cp 1
rs 9.7333
c 0
b 0
f 0
cc 3
nc 3
nop 1
crap 3
1
<?php
2
3
/**
4
 * antibot
5
 *
6
 * @category   Jkphl
7
 * @package    Jkphl\Antibot
8
 * @subpackage Jkphl\Antibot\Domain
9
 * @author     Joschi Kuphal <[email protected]> / @jkphl
10
 * @copyright  Copyright © 2018 Joschi Kuphal <[email protected]> / @jkphl
11
 * @license    http://opensource.org/licenses/MIT The MIT License (MIT)
12
 */
13
14
/***********************************************************************************
15
 *  The MIT License (MIT)
16
 *
17
 *  Copyright © 2018 Joschi Kuphal <[email protected]>
18
 *
19
 *  Permission is hereby granted, free of charge, to any person obtaining a copy of
20
 *  this software and associated documentation files (the "Software"), to deal in
21
 *  the Software without restriction, including without limitation the rights to
22
 *  use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
23
 *  the Software, and to permit persons to whom the Software is furnished to do so,
24
 *  subject to the following conditions:
25
 *
26
 *  The above copyright notice and this permission notice shall be included in all
27
 *  copies or substantial portions of the Software.
28
 *
29
 *  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
30
 *  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
31
 *  FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
32
 *  COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
33
 *  IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
34
 *  CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
35
 ***********************************************************************************/
36
37
namespace Jkphl\Antibot\Domain;
38
39
use Jkphl\Antibot\Domain\Contract\ValidatorInterface;
40
use Jkphl\Antibot\Domain\Exceptions\BlacklistValidationException;
41
use Jkphl\Antibot\Domain\Exceptions\ErrorException;
42
use Jkphl\Antibot\Domain\Exceptions\RuntimeException;
43
use Jkphl\Antibot\Domain\Exceptions\SkippedValidationException;
44
use Jkphl\Antibot\Domain\Exceptions\WhitelistValidationException;
45
use Jkphl\Antibot\Domain\Model\ValidationResult;
46
use Jkphl\Antibot\Infrastructure\Model\InputElement;
47
use Psr\Http\Message\ServerRequestInterface;
48
use Psr\Log\LoggerAwareInterface;
49
use Psr\Log\LoggerInterface;
50
use Psr\Log\NullLogger;
51
52
/**
53
 * Antibot core
54
 *
55
 * @package    Jkphl\Antibot
56
 * @subpackage Jkphl\Antibot\Domain
57
 */
58
class Antibot implements LoggerAwareInterface
59
{
60
    /**
61
     * Session persistent, unique token
62
     *
63
     * @var string
64
     */
65
    protected $unique;
66
    /**
67
     * Antibot prefix
68
     *
69
     * @var string
70
     */
71
    protected $prefix;
72
    /**
73
     * Unique signature
74
     *
75
     * @var string
76
     */
77
    protected $signature;
78
    /**
79
     * Parameter prefix
80
     *
81
     * @var string
82
     */
83
    protected $parameterPrefix;
84
    /**
85
     * GET & POST data
86
     *
87
     * @var null|array
88
     */
89
    protected $data = null;
90
    /**
91
     * Validators
92
     *
93
     * @var ValidatorInterface[]
94
     */
95
    protected $validators = [];
96
    /**
97
     * Immutable instance
98
     *
99
     * @var bool
100
     */
101
    protected $immutable = false;
102
    /**
103
     * Logger
104
     *
105
     * @var LoggerInterface
106
     */
107
    protected $logger = null;
108
    /**
109
     * Default antibot prefix
110
     *
111
     * @var string
112
     */
113
    const DEFAULT_PREFIX = 'antibot';
114
115
    /**
116
     * Antibot constructor
117
     *
118
     * @param string $unique Session-persistent, unique key
119
     * @param string $prefix Prefix
120
     */
121 10
    public function __construct(string $unique, string $prefix = self::DEFAULT_PREFIX)
122
    {
123 10
        $this->unique = $unique;
124 10
        $this->prefix = $prefix;
125 10
        $this->logger = new NullLogger();
126 10
    }
127
128
    /**
129
     * Return the session persistent, unique token
130
     *
131
     * @return string Session persistent, unique token
132
     */
133 4
    public function getUnique(): string
134
    {
135 4
        return $this->unique;
136
    }
137
138
    /**
139
     * Return the prefix
140
     *
141
     * @return string Prefix
142
     */
143 1
    public function getPrefix(): string
144
    {
145 1
        return $this->prefix;
146
    }
147
148
    /**
149
     * Return the submitted Antibot data
150
     *
151
     * @return string[] Antibot data
152
     */
153 4
    public function getData(): ?array
154
    {
155 4
        $this->checkInitialized();
156
157 4
        return $this->data;
158
    }
159
160
    /**
161
     * Return the parameter prefix
162
     *
163
     * @return string Parameter prefix
164
     * @throws RuntimeException If Antibot needs to be initialized
165
     */
166 4
    public function getParameterPrefix(): string
167
    {
168 4
        $this->checkInitialized();
169
170 4
        return $this->parameterPrefix;
171
    }
172
173
    /**
174
     * Validate a request
175
     *
176
     * @param ServerRequestInterface $request Request
177
     *
178
     * @return ValidationResult Validation result
179
     */
180 4
    public function validate(ServerRequestInterface $request): ValidationResult
181
    {
182 4
        $this->logger->info('Start validation');
183 4
        $this->initialize($request);
184 4
        $result = new ValidationResult();
185
186
        // Run through all validators (in order)
187
        /** @var ValidatorInterface $validator */
188 4
        foreach ($this->validators as $validator) {
189
            try {
190 4
                if (!$validator->validate($request, $this)) {
191 3
                    $result->setValid(false);
192
                }
193
194
                // If the validator skipped validation
195 4
            } catch (SkippedValidationException $e) {
196 2
                $result->addSkip($e->getMessage());
197
198
                // If the request failed a blacklist test
199 3
            } catch (BlacklistValidationException $e) {
200
                $result->addBlacklist($e->getMessage());
201
                $result->setValid(false);
202
203
                // If the request passed a whitelist test
204 3
            } catch (WhitelistValidationException $e) {
205 1
                $result->addWhitelist($e->getMessage());
206 1
                break;
207
208
                // If an error occured
209 2
            } catch (ErrorException $e) {
210 2
                $result->addError($e);
211 3
                $result->setValid(false);
212
            }
213
        }
214
215 4
        $this->logger->info('Finished validation');
216
217 4
        return $result;
218
    }
219
220
    /**
221
     * Create and return the raw armor input elements
222
     *
223
     * @param ServerRequestInterface $request Request
224
     *
225
     * @return InputElement[] Armor input elements
226
     */
227 4
    public function armorInputs(ServerRequestInterface $request): array
228
    {
229 4
        $this->initialize($request);
230 4
        $armor = [];
231
232
        // Run through all validators (in order)
233
        /** @var ValidatorInterface $validator */
234 4
        foreach ($this->validators as $validator) {
235 4
            $validatorArmor = $validator->armor($request, $this);
236 4
            if (!empty($validatorArmor)) {
237 4
                $armor = array_merge($armor, $validatorArmor);
238
            }
239
        }
240
241 4
        return $armor;
242
    }
243
244
    /**
245
     * Compare and sort validators
246
     *
247
     * @param ValidatorInterface $validator1 Validator 1
248
     * @param ValidatorInterface $validator2 Validator 2
249
     *
250
     * @return int Sorting
251
     */
252
    protected function sortValidators(ValidatorInterface $validator1, ValidatorInterface $validator2): int
253
    {
254
        $validatorPos1 = $validator1->getPosition();
255
        $validatorPos2 = $validator2->getPosition();
256
        if ($validatorPos1 == $validatorPos2) {
257
            return 0;
258
        }
259
260
        return ($validatorPos1 > $validatorPos2) ? 1 : -1;
261
    }
262
263
    /**
264
     * Pre-validation initialization
265
     *
266
     * @param ServerRequestInterface $request Request
267
     */
268 5
    protected function initialize(ServerRequestInterface $request): void
269
    {
270 5
        usort($this->validators, [$this, 'sortValidators']);
271 5
        $this->immutable       = true;
272 5
        $this->signature       = $this->calculateSignature();
273 5
        $this->parameterPrefix = $this->prefix.'_'.$this->signature;
274 5
        $this->extractData($request);
275 5
    }
276
277
    /**
278
     * Calculate the unique signature
279
     *
280
     * @return string Signature
281
     */
282 5
    protected function calculateSignature(): string
283
    {
284 5
        $params = [$this->prefix, $this->validators];
285
286 5
        return sha1($this->unique.serialize($params));
287
    }
288
289
    /**
290
     * Extract the antibot data from GET and POST parameters
291
     *
292
     * @param ServerRequestInterface $request Request
293
     */
294 5
    protected function extractData(ServerRequestInterface $request): void
295
    {
296 5
        $get        = $request->getQueryParams();
297 5
        $get        = empty($get[$this->parameterPrefix]) ? null : $get[$this->parameterPrefix];
298 5
        $post       = $request->getParsedBody();
299 5
        $post       = empty($post[$this->parameterPrefix]) ? null : $post[$this->parameterPrefix];
300 5
        $this->data = (($get !== null) || ($post !== null)) ? array_merge((array)$get, (array)$post) : null;
301 5
    }
302
303
    /**
304
     * Check whether this Antibot instance is immutable
305
     *
306
     * @throws RuntimeException If the Antibot instance is immutable
307
     */
308 5
    protected function checkImmutable(): void
309
    {
310 5
        if ($this->immutable) {
311
            throw new RuntimeException(
312
                RuntimeException::ANTIBOT_IMMUTABLE_STR,
313
                RuntimeException::ANTIBOT_IMMUTABLE
314
            );
315
        }
316 5
    }
317
318
    /**
319
     * Check whether this Antibot instance is already initialized
320
     *
321
     * @throws RuntimeException If the Antibot instance still needs to be initialized
322
     */
323 4
    protected function checkInitialized(): void
324
    {
325
        // If the Antibot instance still needs to be initialized
326 4
        if (!$this->immutable) {
327
            throw new RuntimeException(
328
                RuntimeException::ANTIBOT_INITIALIZE_STR,
329
                RuntimeException::ANTIBOT_INITIALIZE
330
            );
331
        }
332 4
    }
333
334
    /**
335
     * Return the logger
336
     *
337
     * @return LoggerInterface Logger
338
     */
339 4
    public function getLogger(): LoggerInterface
340
    {
341 4
        return $this->logger;
342
    }
343
344
    /**
345
     * Sets a logger instance on the object
346
     *
347
     * @param LoggerInterface $logger Logger
348
     *
349
     * @return void
350
     */
351 10
    public function setLogger(LoggerInterface $logger): void
352
    {
353 10
        $this->logger = $logger;
354 10
    }
355
}
356