Completed
Push — master ( df4949...df4068 )
by Joschi
03:25
created

Antibot::checkInitialized()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 2.5

Importance

Changes 0
Metric Value
dl 0
loc 10
ccs 3
cts 6
cp 0.5
rs 9.9332
c 0
b 0
f 0
cc 2
nc 2
nop 0
crap 2.5
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 Psr\Http\Message\ServerRequestInterface;
47
use Psr\Log\LoggerAwareInterface;
48
use Psr\Log\LoggerInterface;
49
use Psr\Log\NullLogger;
50
51
/**
52
 * Antibot core
53
 *
54
 * @package    Jkphl\Antibot
55
 * @subpackage Jkphl\Antibot\Domain
56
 */
57
class Antibot implements LoggerAwareInterface
58
{
59
    /**
60
     * Session persistent, unique token
61
     *
62
     * @var string
63
     */
64
    protected $unique;
65
    /**
66
     * Antibot prefix
67
     *
68
     * @var string
69
     */
70
    protected $prefix;
71
    /**
72
     * Unique signature
73
     *
74
     * @var string
75
     */
76
    protected $signature;
77
    /**
78
     * Parameter prefix
79
     *
80
     * @var string
81
     */
82
    protected $parameterPrefix;
83
    /**
84
     * GET & POST data
85
     *
86
     * @var null|string[]
87
     */
88
    protected $data = null;
89
    /**
90
     * Validators
91
     *
92
     * @var ValidatorInterface[]
93
     */
94
    protected $validators = [];
95
    /**
96
     * Immutable instance
97
     *
98
     * @var bool
99
     */
100
    protected $immutable = false;
101
    /**
102
     * Logger
103
     *
104
     * @var LoggerInterface
105
     */
106
    protected $logger = null;
107
    /**
108
     * Default antibot prefix
109
     *
110
     * @var string
111
     */
112
    const DEFAULT_PREFIX = 'antibot';
113
114
    /**
115
     * Antibot constructor
116
     *
117
     * @param string $unique Session-persistent, unique key
118
     * @param string $prefix Prefix
119
     */
120 10
    public function __construct(string $unique, string $prefix = self::DEFAULT_PREFIX)
121
    {
122 10
        $this->unique = $unique;
123 10
        $this->prefix = $prefix;
124 10
        $this->logger = new NullLogger();
125 10
    }
126
127
    /**
128
     * Return the session persistent, unique token
129
     *
130
     * @return string Session persistent, unique token
131
     */
132 4
    public function getUnique(): string
133
    {
134 4
        return $this->unique;
135
    }
136
137
    /**
138
     * Return the prefix
139
     *
140
     * @return string Prefix
141
     */
142 1
    public function getPrefix(): string
143
    {
144 1
        return $this->prefix;
145
    }
146
147
    /**
148
     * Return the submitted Antibot data
149
     *
150
     * @return string[] Antibot data
151
     */
152 4
    public function getData(): ?array
153
    {
154 4
        $this->checkInitialized();
155
156 4
        return $this->data;
157
    }
158
159
    /**
160
     * Return the parameter prefix
161
     *
162
     * @return string Parameter prefix
163
     * @throws RuntimeException If Antibot needs to be initialized
164
     */
165 4
    public function getParameterPrefix(): string
166
    {
167 4
        $this->checkInitialized();
168
169 4
        return $this->parameterPrefix;
170
    }
171
172
    /**
173
     * Validate a request
174
     *
175
     * @param ServerRequestInterface $request Request
176
     *
177
     * @return ValidationResult Validation result
178
     */
179 4
    public function validate(ServerRequestInterface $request): ValidationResult
180
    {
181 4
        $this->logger->info('Start validation');
182 4
        $this->initialize($request);
183 4
        $result = new ValidationResult();
184
185
        // Run through all validators (in order)
186
        /** @var ValidatorInterface $validator */
187 4
        foreach ($this->validators as $validator) {
188
            try {
189 4
                if (!$validator->validate($request, $this)) {
190 3
                    $result->setValid(false);
191
                }
192
193
                // If the validator skipped validation
194 4
            } catch (SkippedValidationException $e) {
195 2
                $result->addSkip($e->getMessage());
196
197
                // If the request failed a blacklist test
198 3
            } catch (BlacklistValidationException $e) {
199
                $result->addBlacklist($e->getMessage());
200
                $result->setValid(false);
201
202
                // If the request passed a whitelist test
203 3
            } catch (WhitelistValidationException $e) {
204 1
                $result->addWhitelist($e->getMessage());
205 1
                break;
206
207
                // If an error occured
208 2
            } catch (ErrorException $e) {
209 2
                $result->addError($e);
210 3
                $result->setValid(false);
211
            }
212
        }
213
214 4
        $this->logger->info('Finished validation');
215 4
        return $result;
216
    }
217
218
    /**
219
     * Return the Antibot armor
220
     *
221
     * @param ServerRequestInterface $request Request
222
     * @param bool $raw                       Return input elements
223
     *
224
     * @return string|array Antibot armor
225
     */
226 4
    public function armor(ServerRequestInterface $request, bool $raw = false)
227
    {
228 4
        $this->initialize($request);
229 4
        $armor = [];
230
231
        // Run through all validators (in order)
232
        /** @var ValidatorInterface $validator */
233 4
        foreach ($this->validators as $validator) {
234 4
            $validatorArmor = $validator->armor($request, $this);
235 4
            if (!empty($validatorArmor)) {
236 4
                $armor = array_merge($armor, $validatorArmor);
237
            }
238
        }
239
240 4
        return $raw ? $armor : implode('', array_map('strval', $armor));
241
    }
242
243
    /**
244
     * Compare and sort validators
245
     *
246
     * @param ValidatorInterface $validator1 Validator 1
247
     * @param ValidatorInterface $validator2 Validator 2
248
     *
249
     * @return int Sorting
250
     */
251
    protected function sortValidators(ValidatorInterface $validator1, ValidatorInterface $validator2): int
252
    {
253
        $validatorPos1 = $validator1->getPosition();
254
        $validatorPos2 = $validator2->getPosition();
255
        if ($validatorPos1 == $validatorPos2) {
256
            return 0;
257
        }
258
259
        return ($validatorPos1 > $validatorPos2) ? 1 : -1;
260
    }
261
262
    /**
263
     * Pre-validation initialization
264
     *
265
     * @param ServerRequestInterface $request Request
266
     */
267 5
    protected function initialize(ServerRequestInterface $request): void
268
    {
269 5
        usort($this->validators, [$this, 'sortValidators']);
270 5
        $this->immutable       = true;
271 5
        $this->signature       = $this->calculateSignature();
272 5
        $this->parameterPrefix = $this->prefix.'_'.$this->signature;
273 5
        $this->extractData($request);
274 5
    }
275
276
    /**
277
     * Calculate the unique signature
278
     *
279
     * @return string Signature
280
     */
281 5
    protected function calculateSignature(): string
282
    {
283 5
        $params = [$this->prefix, $this->validators];
284
285 5
        return sha1($this->unique.serialize($params));
286
    }
287
288
    /**
289
     * Extract the antibot data from GET and POST parameters
290
     *
291
     * @param ServerRequestInterface $request Request
292
     */
293 5
    protected function extractData(ServerRequestInterface $request): void
294
    {
295 5
        $get        = $request->getQueryParams();
296 5
        $get        = empty($get[$this->parameterPrefix]) ? null : $get[$this->parameterPrefix];
297 5
        $post       = $request->getParsedBody();
298 5
        $post       = empty($post[$this->parameterPrefix]) ? null : $post[$this->parameterPrefix];
299 5
        $this->data = (($get !== null) || ($post !== null)) ? array_merge((array)$get, (array)$post) : null;
0 ignored issues
show
Documentation Bug introduced by
It seems like $get !== null || $post !..., (array) $post) : null can also be of type array. However, the property $data is declared as type null|array<integer,string>. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
300 5
    }
301
302
    /**
303
     * Check whether this Antibot instance is immutable
304
     *
305
     * @throws RuntimeException If the Antibot instance is immutable
306
     */
307 5
    protected function checkImmutable(): void
308
    {
309 5
        if ($this->immutable) {
310
            throw new RuntimeException(
311
                RuntimeException::ANTIBOT_IMMUTABLE_STR,
312
                RuntimeException::ANTIBOT_IMMUTABLE
313
            );
314
        }
315 5
    }
316
317
    /**
318
     * Check whether this Antibot instance is already initialized
319
     *
320
     * @throws RuntimeException If the Antibot instance still needs to be initialized
321
     */
322 4
    protected function checkInitialized(): void
323
    {
324
        // If the Antibot instance still needs to be initialized
325 4
        if (!$this->immutable) {
326
            throw new RuntimeException(
327
                RuntimeException::ANTIBOT_INITIALIZE_STR,
328
                RuntimeException::ANTIBOT_INITIALIZE
329
            );
330
        }
331 4
    }
332
333
    /**
334
     * Return the logger
335
     *
336
     * @return LoggerInterface Logger
337
     */
338 4
    public function getLogger(): LoggerInterface
339
    {
340 4
        return $this->logger;
341
    }
342
343
    /**
344
     * Sets a logger instance on the object
345
     *
346
     * @param LoggerInterface $logger Logger
347
     *
348
     * @return void
349
     */
350 10
    public function setLogger(LoggerInterface $logger): void
351
    {
352 10
        $this->logger = $logger;
353 10
    }
354
}
355