Completed
Push — master ( ac18c1...db3c82 )
by Joschi
03:03
created

Antibot   A

Complexity

Total Complexity 26

Size/Duplication

Total Lines 256
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 6

Test Coverage

Coverage 80%

Importance

Changes 0
Metric Value
wmc 26
lcom 1
cbo 6
dl 0
loc 256
ccs 52
cts 65
cp 0.8
rs 10
c 0
b 0
f 0

12 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 6 1
A getPrefix() 0 4 1
A getParameterPrefix() 0 9 2
A getUnique() 0 4 1
A getData() 0 4 1
A validate() 0 27 5
A armor() 0 16 3
A sortValidators() 0 10 3
A initialize() 0 8 1
A calculateSignature() 0 6 1
A extractData() 0 8 5
A checkImmutable() 0 9 2
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\ActorInterface;
40
use Jkphl\Antibot\Domain\Contract\ValidatorInterface;
41
use Jkphl\Antibot\Domain\Exceptions\BlacklistValidationException;
42
use Jkphl\Antibot\Domain\Exceptions\RuntimeException;
43
use Jkphl\Antibot\Domain\Exceptions\WhitelistValidationException;
44
use Jkphl\Antibot\Domain\Model\ValidationResult;
45
use Psr\Http\Message\ServerRequestInterface;
46
47
/**
48
 * Antibot core
49
 *
50
 * @package    Jkphl\Antibot
51
 * @subpackage Jkphl\Antibot\Domain
52
 */
53
class Antibot
54
{
55
    /**
56
     * Antibot configuration
57
     *
58
     * @var array
59
     */
60
    protected $config;
61
    /**
62
     * Session persistent, unique token
63
     *
64
     * @var string
65
     */
66
    protected $unique;
67
    /**
68
     * Antibot prefix
69
     *
70
     * @var string
71
     */
72
    protected $prefix;
73
    /**
74
     * Unique signature
75
     *
76
     * @var string
77
     */
78
    protected $signature;
79
    /**
80
     * Parameter prefix
81
     *
82
     * @var string
83
     */
84
    protected $parameterPrefix;
85
    /**
86
     * GET & POST data
87
     *
88
     * @var null|string[]
89
     */
90
    protected $data = null;
91
    /**
92
     * Validators
93
     *
94
     * @var ValidatorInterface[]
95
     */
96
    protected $validators = [];
97
    /**
98
     * Actors
99
     *
100
     * @var ActorInterface[]
101
     */
102
    protected $actors = [];
103
    /**
104
     * Immutable instance
105
     *
106
     * @var bool
107
     */
108
    protected $immutable = false;
109
    /**
110
     * Default antibot prefix
111
     *
112
     * @var string
113
     */
114
    const DEFAULT_PREFIX = 'antibot';
115
116
    /**
117
     * Antibot constructor
118
     *
119
     * @param array $config  Configuration
120
     * @param string $unique Session-persistent, unique key
121
     * @param string $prefix Prefix
122
     */
123 7
    public function __construct($config, string $unique, string $prefix = self::DEFAULT_PREFIX)
124
    {
125 7
        $this->config = $config;
126 7
        $this->unique = $unique;
127 7
        $this->prefix = $prefix;
128 7
    }
129
130
    /**
131
     * Return the prefix
132
     *
133
     * @return string Prefix
134
     */
135 1
    public function getPrefix(): string
136
    {
137 1
        return $this->prefix;
138
    }
139
140
    /**
141
     * Return the parameter prefix
142
     *
143
     * @return string Parameter prefix
144
     * @throws RuntimeException If Antibot needs to be initialized
145
     */
146 1
    public function getParameterPrefix(): string
147
    {
148
        // If Antibot needs to be initialized
149 1
        if (!$this->immutable) {
150
            throw new RuntimeException(RuntimeException::ANTIBOT_INITIALIZE_STR, RuntimeException::ANTIBOT_INITIALIZE);
151
        }
152
153 1
        return $this->parameterPrefix;
154
    }
155
156
    /**
157
     * Return the session persistent, unique token
158
     *
159
     * @return string Session persistent, unique token
160
     */
161 1
    public function getUnique(): string
162
    {
163 1
        return $this->unique;
164
    }
165
166
    /**
167
     * Return the submitted Antibot data
168
     *
169
     * @return string[] Antibot data
170
     */
171 1
    public function getData(): ?array
172
    {
173 1
        return $this->data;
174
    }
175
176
    /**
177
     * Validate a request
178
     *
179
     * @param ServerRequestInterface $request Request
180
     *
181
     * @return ValidationResult Validation result
182
     */
183 1
    public function validate(ServerRequestInterface $request): ValidationResult
184
    {
185 1
        $this->initialize($request);
186 1
        $result = new ValidationResult();
187
188
        // Run through all validators (in order)
189
        /** @var ValidatorInterface $validator */
190 1
        foreach ($this->validators as $validator) {
191
            try {
192 1
                if (!$validator->validate($request, $this)) {
193
                    $result->setValid(false);
194
                }
195
196
                // If the request failed a blacklist test
197 1
            } catch (BlacklistValidationException $e) {
198
                $result->addBlacklist($e->getMessage());
199
                $result->setValid(false);
200
201
                // If the request passed a whitelist test
202 1
            } catch (WhitelistValidationException $e) {
203 1
                $result->addWhitelist($e->getMessage());
204 1
                break;
205
            }
206
        }
207
208 1
        return $result;
209
    }
210
211
    /**
212
     * Return the Antibot armor
213
     *
214
     * @param ServerRequestInterface $request Request
215
     *
216
     * @return string Antibot armor
217
     */
218 1
    public function armor(ServerRequestInterface $request): string
219
    {
220 1
        $this->initialize($request);
221 1
        $armor = [];
222
223
        // Run through all validators (in order)
224
        /** @var ValidatorInterface $validator */
225 1
        foreach ($this->validators as $validator) {
226 1
            $validatorArmor = $validator->armor($request, $this);
227 1
            if (!empty($validatorArmor)) {
228 1
                $armor[] = $validatorArmor;
229
            }
230
        }
231
232 1
        return implode('', $armor);
233
    }
234
235
    /**
236
     * Compare and sort validators
237
     *
238
     * @param ValidatorInterface $validator1 Validator 1
239
     * @param ValidatorInterface $validator2 Validator 2
240
     *
241
     * @return int Sorting
242
     */
243
    protected function sortValidators(ValidatorInterface $validator1, ValidatorInterface $validator2): int
244
    {
245
        $validatorPos1 = $validator1->getPosition();
246
        $validatorPos2 = $validator2->getPosition();
247
        if ($validatorPos1 == $validatorPos2) {
248
            return 0;
249
        }
250
251
        return ($validatorPos1 > $validatorPos2) ? 1 : -1;
252
    }
253
254
    /**
255
     * Pre-validation initialization
256
     *
257
     * @param ServerRequestInterface $request Request
258
     */
259 2
    protected function initialize(ServerRequestInterface $request): void
260
    {
261 2
        usort($this->validators, [$this, 'sortValidators']);
262 2
        $this->immutable       = true;
263 2
        $this->signature       = $this->calculateSignature();
264 2
        $this->parameterPrefix = $this->prefix.'_'.$this->signature;
265 2
        $this->extractData($request);
266 2
    }
267
268
    /**
269
     * Calculate the unique signature
270
     *
271
     * @return string Signature
272
     */
273 2
    protected function calculateSignature(): string
274
    {
275 2
        $params = [$this->prefix, $this->config, $this->validators, $this->actors];
276
277 2
        return sha1($this->unique.serialize($params));
278
    }
279
280
    /**
281
     * Extract the antibot data from GET and POST parameters
282
     *
283
     * @param ServerRequestInterface $request Request
284
     */
285 2
    protected function extractData(ServerRequestInterface $request): void
286
    {
287 2
        $get        = $request->getQueryParams();
288 2
        $get        = empty($get[$this->parameterPrefix]) ? null : $get[$this->parameterPrefix];
289 2
        $post       = $request->getParsedBody();
290 2
        $post       = empty($post[$this->parameterPrefix]) ? null : $post[$this->parameterPrefix];
291 2
        $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...
292 2
    }
293
294
    /**
295
     * Check whether this Antibot instance is immutable
296
     *
297
     * @throws RuntimeException If the Antibot instance is immutable
298
     */
299 2
    protected function checkImmutable(): void
300
    {
301 2
        if ($this->immutable) {
302
            throw new RuntimeException(
303
                RuntimeException::ANTIBOT_IMMUTABLE_STR,
304
                RuntimeException::ANTIBOT_IMMUTABLE
305
            );
306
        }
307 2
    }
308
}
309