UserDataProcessor::processChanges()   B
last analyzed

Complexity

Conditions 9
Paths 21

Size

Total Lines 23
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 90

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 9
eloc 16
nc 21
nop 2
dl 0
loc 23
ccs 0
cts 16
cp 0
crap 90
rs 8.0555
c 1
b 0
f 0
1
<?php
2
3
/*
4
 * This file is part of the Silverback API Components Bundle Project
5
 *
6
 * (c) Daniel West <[email protected]>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
declare(strict_types=1);
13
14
namespace Silverback\ApiComponentsBundle\Helper\User;
15
16
use Silverback\ApiComponentsBundle\Entity\User\AbstractUser;
17
use Silverback\ApiComponentsBundle\Exception\InvalidArgumentException;
18
use Silverback\ApiComponentsBundle\Exception\UnexpectedValueException;
19
use Silverback\ApiComponentsBundle\Repository\User\UserRepositoryInterface;
20
use Silverback\ApiComponentsBundle\Security\TokenGenerator;
21
use Symfony\Component\PasswordHasher\Hasher\PasswordHasherFactoryInterface;
22
use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;
23
24
/**
25
 * @author Daniel West <[email protected]>
26
 */
27
class UserDataProcessor
28
{
29
    public function __construct(
30
        private readonly UserPasswordHasherInterface $passwordHasher,
31
        private readonly UserRepositoryInterface $userRepository,
32
        private readonly PasswordHasherFactoryInterface $passwordHasherFactory,
33
        private readonly bool $initialEmailVerifiedState,
34
        private readonly bool $verifyEmailOnRegister,
35
        private readonly bool $verifyEmailOnChange,
36
        private readonly int $tokenTtl = 8600
37
    ) {
38
    }
39
40
    public function updatePasswordConfirmationToken(string $usernameQuery): ?AbstractUser
41
    {
42
        $user = $this->userRepository->loadUserByIdentifier($usernameQuery);
43
        if (!$user) {
44
            throw new InvalidArgumentException('Username not found');
45
        }
46
47
        if ($user->isPasswordRequestLimitReached($this->tokenTtl)) {
48
            return null;
49
        }
50
51
        $username = $user->getUsername();
52
        if (!$username) {
53
            throw new UnexpectedValueException(sprintf('The entity %s should have a username set to send a password reset email.', AbstractUser::class));
54
        }
55
        $user->setNewPasswordConfirmationToken($this->passwordHasher->hashPassword($user, $token = TokenGenerator::generateToken()));
56
        $user->plainNewPasswordConfirmationToken = $token;
57
58
        return $user;
59
    }
60
61
    public function passwordReset(string $username, string $token, string $newPassword): ?AbstractUser
62
    {
63
        $user = $this->userRepository->findOneWithPasswordResetToken($username);
64
        if (!$user) {
65
            return null;
66
        }
67
        $hasher = $this->passwordHasherFactory->getPasswordHasher($user);
68
        if (!$hasher->verify($user->getNewPasswordConfirmationToken(), $token)) {
0 ignored issues
show
Bug introduced by
It seems like $user->getNewPasswordConfirmationToken() can also be of type null; however, parameter $hashedPassword of Symfony\Component\Passwo...sherInterface::verify() 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

68
        if (!$hasher->verify(/** @scrutinizer ignore-type */ $user->getNewPasswordConfirmationToken(), $token)) {
Loading history...
69
            return null;
70
        }
71
72
        $user->setPlainPassword($newPassword);
73
        $user->setNewPasswordConfirmationToken(null);
74
        $user->setPasswordRequestedAt(null);
75
        $this->hashPassword($user);
76
77
        return $user;
78
    }
79
80
    public function processChanges(AbstractUser $user, ?AbstractUser $previousUser): void
81
    {
82
        $this->hashPassword($user);
83
        if (!$previousUser) {
84
            $user->setEmailAddressVerified($this->initialEmailVerifiedState);
85
            if (!$this->initialEmailVerifiedState && $this->verifyEmailOnRegister) {
86
                $user->setEmailAddressVerifyToken($this->passwordHasher->hashPassword($user, $token = TokenGenerator::generateToken()));
87
                $user->plainEmailAddressVerifyToken = $token;
88
            }
89
        }
90
91
        if ($previousUser) {
92
            if ($this->verifyEmailOnChange && $user->getEmailAddress() !== $previousUser->getEmailAddress()) {
93
                $user->setEmailAddressVerifyToken($this->passwordHasher->hashPassword($user, $token = TokenGenerator::generateToken()));
94
                $user->plainEmailAddressVerifyToken = $token;
95
            }
96
            if (($newEmail = $user->getNewEmailAddress()) !== $previousUser->getNewEmailAddress()) {
97
                if ($newEmail) {
98
                    $user->setNewEmailConfirmationToken($this->passwordHasher->hashPassword($user, $token = TokenGenerator::generateToken()));
99
                    $user->plainNewEmailConfirmationToken = $token;
100
                } else {
101
                    // invalidate any existing requests
102
                    $user->setNewEmailConfirmationToken(null);
103
                }
104
            }
105
        }
106
    }
107
108
    private function hashPassword(AbstractUser $entity): void
109
    {
110
        if (!$entity->getPlainPassword()) {
111
            return;
112
        }
113
        $encoded = $this->passwordHasher->hashPassword($entity, $entity->getPlainPassword());
114
        $entity->setPassword($encoded);
115
        $entity->eraseCredentials();
116
    }
117
}
118