Completed
Branch TYPO3v9 (42c67e)
by Tomas Norre
17:38
created

RestrictionService::getLogger()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 2.0625

Importance

Changes 0
Metric Value
dl 0
loc 7
ccs 3
cts 4
cp 0.75
rs 10
c 0
b 0
f 0
cc 2
nc 2
nop 0
crap 2.0625
1
<?php
2
namespace Aoe\FeloginBruteforceProtection\Domain\Service;
3
4
/***************************************************************
5
 *  Copyright notice
6
 *
7
 *  (c) 2015 AOE GmbH, <[email protected]>
8
 *  (c) 2014 André Wuttig <[email protected]>, portrino GmbH
9
 *
10
 *  All rights reserved
11
 *
12
 *  This script is part of the TYPO3 project. The TYPO3 project is
13
 *  free software; you can redistribute it and/or modify
14
 *  it under the terms of the GNU General Public License as published by
15
 *  the Free Software Foundation; either version 3 of the License, or
16
 *  (at your option) any later version.
17
 *
18
 *  The GNU General Public License can be found at
19
 *  http://www.gnu.org/copyleft/gpl.html.
20
 *
21
 *  This script is distributed in the hope that it will be useful,
22
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
23
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
24
 *  GNU General Public License for more details.
25
 *
26
 *  This copyright notice MUST APPEAR in all copies of the script!
27
 ***************************************************************/
28
29
use Aoe\FeloginBruteforceProtection\Domain\Repository\EntryRepository;
30
use Aoe\FeloginBruteforceProtection\Service\Logger\LoggerInterface;
31
use Aoe\FeloginBruteforceProtection\System\Configuration;
32
use TYPO3\CMS\Core\Utility\GeneralUtility;
33
use Aoe\FeloginBruteforceProtection\Domain\Model\Entry;
34
use Aoe\FeloginBruteforceProtection\Service\Logger\Logger;
35
use Aoe\FeloginBruteforceProtection\Service\FeLoginBruteForceApi\FeLoginBruteForceApi;
36
use TYPO3\CMS\Extbase\Object\ObjectManager;
37
use TYPO3\CMS\Extbase\Persistence\Generic\PersistenceManager;
38
39
/**
40
 *
41
 * @package Aoe\\FeloginBruteforceProtection\\Domain\\Service
42
 *
43
 * @author Kevin Schu <[email protected]>
44
 * @author Timo Fuchs <[email protected]>
45
 * @author Andre Wuttig <[email protected]>
46
 *
47
 */
48
class RestrictionService
49
{
50
    /**
51
     * @var boolean
52
     */
53
    protected static $preventFailureCount = false;
54
55
    /**
56
     * @var RestrictionIdentifierInterface
57
     */
58
    protected $restrictionIdentifier;
59
60
    /**
61
     * @var string
62
     */
63
    protected $clientIdentifier;
64
65
    /**
66
     * @var \Aoe\FeloginBruteforceProtection\System\Configuration
67
     */
68
    protected $configuration;
69
70
    /**
71
     * @var \Aoe\FeloginBruteforceProtection\Domain\Repository\EntryRepository
72
     */
73
    protected $entryRepository;
74
75
    /**
76
     * @var \TYPO3\CMS\Extbase\Persistence\Generic\PersistenceManager
77
     */
78
    protected $persistenceManager;
79
80
    /**
81
     * @var \TYPO3\CMS\Extbase\Object\ObjectManagerInterface
82
     */
83
    protected $objectManager;
84
85
    /**
86
     * @var Entry
87
     */
88
    protected $entry;
89
90
    /**
91
     * @var boolean
92
     */
93
    protected $clientRestricted;
94
95
    /**
96
     * @var Logger
97
     */
98
    protected $logger;
99
100
    /**
101
     * @var FeLoginBruteForceApi
102
     */
103
    protected $feLoginBruteForceApi;
104
105
    /**
106
     * @param RestrictionIdentifierInterface $restrictionIdentifier
107
     */
108 26
    public function __construct(RestrictionIdentifierInterface $restrictionIdentifier)
109
    {
110 26
        $this->objectManager = GeneralUtility::makeInstance(ObjectManager::class);
111 26
        $this->restrictionIdentifier = $restrictionIdentifier;
112
113 26
        $this->configuration = $this->objectManager->get(Configuration::class);
114 26
        $this->persistenceManager = $this->objectManager->get(PersistenceManager::class);
115 26
        $this->entryRepository = $this->objectManager->get(EntryRepository::class);
116
117
118 26
    }
119
120
    /**
121
     * @param boolean $preventFailureCount
122
     * @return void
123
     */
124
    public static function setPreventFailureCount($preventFailureCount)
125
    {
126
        self::$preventFailureCount = $preventFailureCount;
127
    }
128
129
    /**
130
     * @return boolean
131
     */
132 16
    public function isClientRestricted()
133
    {
134 16
        if (false === isset($this->clientRestricted)) {
135 16
            if ($this->hasEntry() && $this->isRestricted($this->getEntry())) {
0 ignored issues
show
Bug introduced by
It seems like $this->getEntry() can be null; however, isRestricted() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
136 4
                $this->clientRestricted = true;
137
            } else {
138 12
                $this->clientRestricted = false;
139
            }
140
        }
141 16
        return $this->clientRestricted;
142
    }
143
144
    /**
145
     * @return void
146
     */
147 10
    public function removeEntry()
148
    {
149 10
        if ($this->hasEntry()) {
150 10
            $this->entryRepository->remove($this->entry);
151 10
            $this->persistenceManager->persistAll();
152
153 10
            $this->log('Bruteforce Counter removed', LoggerInterface::SEVERITY_INFO);
154
        }
155 10
        $this->clientRestricted = false;
156 10
        unset($this->entry);
157 10
    }
158
159
    /**
160
     * @return void
161
     */
162
    public function checkAndHandleRestriction()
163
    {
164
        if (self::$preventFailureCount) {
165
            return;
166
        }
167
168
        $identifierValue = $this->restrictionIdentifier->getIdentifierValue();
169
        if (empty($identifierValue)) {
170
            return;
171
        }
172
173
        if (false === $this->hasEntry()) {
174
            $this->createEntry();
175
        }
176
177
        if ($this->hasMaximumNumberOfFailuresReached($this->getEntry())) {
0 ignored issues
show
Bug introduced by
It seems like $this->getEntry() can be null; however, hasMaximumNumberOfFailuresReached() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
178
            return;
179
        }
180
181
        $this->entry->increaseFailures();
182
        $this->saveEntry();
183
184
        $this->restrictionLog();
185
    }
186
187
    /**
188
     * @return void
189
     */
190
    protected function restrictionLog()
191
    {
192
        if ($this->getFeLoginBruteForceApi()->shouldCountWithinThisRequest()) {
193
            if ($this->isClientRestricted()) {
194
                $this->log('Bruteforce Protection Locked', LoggerInterface::SEVERITY_WARNING);
195
            } else {
196
                $this->log('Bruteforce Counter increased', LoggerInterface::SEVERITY_NOTICE);
197
            }
198
        } else {
199
            $this->log(
200
                'Bruteforce Counter would increase, but is prohibited by API',
201
                LoggerInterface::SEVERITY_NOTICE
202
            );
203
        }
204
    }
205
206
    /**
207
     * @param $message
208
     * @param $severity
209
     */
210 10
    private function log($message, $severity)
211
    {
212 10
        $failureCount = 0;
213 10
        if ($this->hasEntry()) {
214 10
            $failureCount = $this->getEntry()->getFailures();
215
        }
216 10
        if ($this->isClientRestricted()) {
217
            $restricted = 'Yes';
218
        } else {
219 10
            $restricted = 'No';
220
        }
221
        $additionalData = array(
222 10
            'FAILURE_COUNT' => $failureCount,
223 10
            'RESTRICTED' => $restricted,
224 10
            'REMOTE_ADDR' => GeneralUtility::getIndpEnv('REMOTE_ADDR'),
225 10
            'REQUEST_URI' => GeneralUtility::getIndpEnv('REQUEST_URI'),
226 10
            'HTTP_USER_AGENT' => GeneralUtility::getIndpEnv('HTTP_USER_AGENT')
227
        );
228
229 10
        $this->getLogger()->log($message, $severity, $additionalData, 'felogin_bruteforce_protection');
230 10
    }
231
232
    /**
233
     * @return Logger
234
     */
235 10
    private function getLogger()
236
    {
237 10
        if (!isset($this->logger)) {
238
            $this->logger = new Logger();
239
        }
240 10
        return $this->logger;
241
    }
242
243
    /**
244
     * @return void
245
     */
246
    private function createEntry()
247
    {
248
        /** @var $entry Entry */
249
        $this->entry = $this->objectManager->get('Aoe\FeloginBruteforceProtection\Domain\Model\Entry');
250
        $this->entry->setFailures(0);
251
        $this->entry->setCrdate(time());
252
        $this->entry->setTstamp(time());
253
        $this->entry->setIdentifier($this->getClientIdentifier());
254
        $this->entryRepository->add($this->entry);
255
        $this->persistenceManager->persistAll();
256
        $this->clientRestricted = false;
257
    }
258
259
    /**
260
     * @return void
261
     */
262
    private function saveEntry()
263
    {
264
        if ($this->entry->getFailures() > 0) {
265
            $this->entry->setTstamp(time());
266
        }
267
        $this->entryRepository->add($this->entry);
268
        $this->persistenceManager->persistAll();
269
        if ($this->hasMaximumNumberOfFailuresReached($this->entry)) {
270
            $this->clientRestricted = true;
271
        }
272
    }
273
274
    /**
275
     * @param Entry $entry
276
     * @return boolean
277
     */
278 16
    private function isRestricted(Entry $entry)
279
    {
280 16
        if ($this->hasMaximumNumberOfFailuresReached($entry)) {
281 8
            if (false === $this->isRestrictionTimeReached($entry)) {
282 4
                return true;
283
            }
284
        }
285 12
        return false;
286
    }
287
288
    /**
289
     * @return boolean
290
     */
291 16
    public function hasEntry()
292
    {
293 16
        return ($this->getEntry() instanceof Entry);
294
    }
295
296
    /**
297
     * @return Entry|null
298
     */
299 16
    public function getEntry()
300
    {
301 16
        if (false === isset($this->entry)) {
302 16
            $entry = $this->entryRepository->findByIdentifier($this->getClientIdentifier());
303 16
            if ($entry instanceof Entry) {
304 16
                $this->entry = $entry;
305 16
                if ($this->isOutdated($entry)) {
306 10
                    $this->removeEntry();
307
                }
308
            }
309
        }
310 16
        return $this->entry;
311
    }
312
313
    /**
314
     * @param Entry $entry
315
     * @return boolean
316
     */
317 16
    private function isOutdated(Entry $entry)
318
    {
319
        return (
320 16
            ($this->hasMaximumNumberOfFailuresReached($entry) && $this->isRestrictionTimeReached($entry)) ||
321 16
            (false === $this->hasMaximumNumberOfFailuresReached($entry) && $this->isResetTimeOver($entry))
322
        );
323
    }
324
325
    /**
326
     * @param Entry $entry
327
     * @return boolean
328
     */
329 8
    private function isResetTimeOver(Entry $entry)
330
    {
331 8
        return ($entry->getCrdate() < time() - $this->configuration->getResetTime());
332
    }
333
334
    /**
335
     * @param Entry $entry
336
     * @return boolean
337
     */
338 16
    private function hasMaximumNumberOfFailuresReached(Entry $entry)
339
    {
340 16
        return ($entry->getFailures() >= $this->configuration->getMaximumNumberOfFailures());
341
    }
342
343
    /**
344
     * @param Entry $entry
345
     * @return boolean
346
     */
347 8
    private function isRestrictionTimeReached(Entry $entry)
348
    {
349 8
        return ($entry->getTstamp() < time() - $this->configuration->getRestrictionTime());
350
    }
351
352
    /**
353
     * Returns the client identifier based on the clients IP address.
354
     *
355
     * @return string
356
     */
357 16
    private function getClientIdentifier()
358
    {
359 16
        if (false === isset($this->clientIdentifier)) {
360 16
            $this->clientIdentifier = md5(
361 16
                $this->restrictionIdentifier->getIdentifierValue() . $GLOBALS['TYPO3_CONF_VARS']['SYS']['encryptionKey']
362
            );
363
        }
364 16
        return $this->clientIdentifier;
365
    }
366
367
    /**
368
     * @return FeLoginBruteForceApi
369
     */
370
    protected function getFeLoginBruteForceApi()
371
    {
372
        if (!isset($this->feLoginBruteForceApi)) {
373
            $this->feLoginBruteForceApi = $this->objectManager->get(
374
                'Aoe\FeloginBruteforceProtection\Service\FeLoginBruteForceApi\FeLoginBruteForceApi'
375
            );
376
        }
377
        return $this->feLoginBruteForceApi;
378
    }
379
}
380