Passed
Pull Request — master (#2043)
by Tarmo
09:30
created

LockedUserSubscriber::onAuthenticationSuccess()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 9
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 2

Importance

Changes 0
Metric Value
cc 2
eloc 4
nc 2
nop 1
dl 0
loc 9
ccs 5
cts 5
cp 1
crap 2
rs 10
c 0
b 0
f 0
1
<?php
2
declare(strict_types = 1);
3
/**
4
 * /src/EventSubscriber/LockedUserSubscriber.php
5
 *
6
 * @author TLe, Tarmo Leppänen <[email protected]>
7
 */
8
9
namespace App\EventSubscriber;
10
11
use App\Entity\LogLoginFailure;
12
use App\Entity\User;
13
use App\Repository\UserRepository;
14
use App\Resource\LogLoginFailureResource;
15
use App\Security\SecurityUser;
16
use Lexik\Bundle\JWTAuthenticationBundle\Event\AuthenticationFailureEvent;
17
use Lexik\Bundle\JWTAuthenticationBundle\Event\AuthenticationSuccessEvent;
18
use Lexik\Bundle\JWTAuthenticationBundle\Events;
19
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
20
use Symfony\Component\HttpFoundation\Request;
21
use Symfony\Component\HttpFoundation\RequestStack;
22
use Symfony\Component\Security\Core\Exception\LockedException;
23
use Symfony\Component\Security\Core\Exception\UnsupportedUserException;
24
use Throwable;
25
use function assert;
26
use function count;
27
use function is_string;
28
29
/**
30
 * Class LockedUserSubscriber
31
 *
32
 * @package App\EventSubscriber
33
 * @author TLe, Tarmo Leppänen <[email protected]>
34
 */
35
class LockedUserSubscriber implements EventSubscriberInterface
36
{
37 22
    public function __construct(
38
        private readonly UserRepository $userRepository,
39
        private readonly LogLoginFailureResource $logLoginFailureResource,
40
        private readonly RequestStack $requestStack,
41
    ) {
42
    }
43
44
    /**
45
     * {@inheritdoc}
46
     *
47
     * @return array<string, string|array<int, string|int>>
48
     */
49 1
    public static function getSubscribedEvents(): array
50
    {
51
        return [
52
            AuthenticationSuccessEvent::class => [
53 1
                'onAuthenticationSuccess',
54
                128,
55
            ],
56
            Events::AUTHENTICATION_SUCCESS => [
57
                'onAuthenticationSuccess',
58
                128,
59
            ],
60
            AuthenticationFailureEvent::class => 'onAuthenticationFailure',
61
            Events::AUTHENTICATION_FAILURE => 'onAuthenticationFailure',
62
        ];
63
    }
64
65
    /**
66
     * @throws Throwable
67
     */
68 18
    public function onAuthenticationSuccess(AuthenticationSuccessEvent $event): void
69
    {
70 18
        $user = $this->getUser($event->getUser()) ?? throw new UnsupportedUserException('Unsupported user.');
71
72 17
        if (count($user->getLogsLoginFailure()) > 10) {
73 2
            throw new LockedException('Locked account.');
74
        }
75
76 15
        $this->logLoginFailureResource->reset($user);
77
    }
78
79
    /**
80
     * @throws Throwable
81
     */
82 5
    public function onAuthenticationFailure(): void
83
    {
84 5
        $request = $this->requestStack->getCurrentRequest();
85
86
        assert($request instanceof Request);
87
88 5
        $user = $this->getUser(
89 5
            (string)($request->query->get('username') ?? $request->request->get('username', ''))
90
        );
91
92 5
        if ($user !== null) {
93 3
            $this->logLoginFailureResource->save(new LogLoginFailure($user), true);
94
        }
95
    }
96
97
    /**
98
     * @throws Throwable
99
     */
100 22
    private function getUser(string | object $user): ?User
101
    {
102
        return match (true) {
103 22
            is_string($user) => $this->userRepository->loadUserByIdentifier($user, false),
0 ignored issues
show
Bug introduced by
It seems like $user can also be of type object; however, parameter $username of App\Repository\UserRepos...:loadUserByIdentifier() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

103
            is_string($user) => $this->userRepository->loadUserByIdentifier(/** @scrutinizer ignore-type */ $user, false),
Loading history...
104
            $user instanceof SecurityUser =>
105 17
                $this->userRepository->loadUserByIdentifier($user->getUserIdentifier(), true),
106 22
            default => null,
107
        };
108
    }
109
}
110