Test Failed
Push — master ( 61801d...f3c992 )
by Daniel
11:05
created

UserDataProcessor   A

Complexity

Total Complexity 19

Size/Duplication

Total Lines 107
Duplicated Lines 0 %

Test Coverage

Coverage 0%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 60
dl 0
loc 107
ccs 0
cts 58
cp 0
rs 10
c 1
b 0
f 0
wmc 19

5 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 16 1
A passwordReset() 0 17 3
A updatePasswordConfirmationToken() 0 20 4
B processChanges() 0 23 9
A hashPassword() 0 10 2
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
    private UserPasswordHasherInterface $passwordHasher;
30
    private UserRepositoryInterface $userRepository;
31
    private PasswordHasherFactoryInterface $passwordHasherFactory;
32
    private bool $initialEmailVerifiedState;
33
    private bool $verifyEmailOnRegister;
34
    private bool $verifyEmailOnChange;
35
    private int $tokenTtl;
36
37
    public function __construct(
38
        UserPasswordHasherInterface $passwordHasher,
39
        UserRepositoryInterface $userRepository,
40
        PasswordHasherFactoryInterface $passwordHasherFactory,
41
        bool $initialEmailVerifiedState,
42
        bool $verifyEmailOnRegister,
43
        bool $verifyEmailOnChange,
44
        int $tokenTtl = 8600
45
    ) {
46
        $this->passwordHasher = $passwordHasher;
47
        $this->userRepository = $userRepository;
48
        $this->passwordHasherFactory = $passwordHasherFactory;
49
        $this->initialEmailVerifiedState = $initialEmailVerifiedState;
50
        $this->verifyEmailOnRegister = $verifyEmailOnRegister;
51
        $this->verifyEmailOnChange = $verifyEmailOnChange;
52
        $this->tokenTtl = $tokenTtl;
53
    }
54
55
    public function updatePasswordConfirmationToken(string $usernameQuery): ?AbstractUser
56
    {
57
        $user = $this->userRepository->findOneBy(['username' => $usernameQuery]);
0 ignored issues
show
Bug introduced by
The method findOneBy() does not exist on Silverback\ApiComponents...UserRepositoryInterface. Did you maybe mean findOneByEmail()? ( Ignorable by Annotation )

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

57
        /** @scrutinizer ignore-call */ 
58
        $user = $this->userRepository->findOneBy(['username' => $usernameQuery]);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
58
        if (!$user) {
59
            throw new InvalidArgumentException('Username not found');
60
        }
61
62
        if ($user->isPasswordRequestLimitReached($this->tokenTtl)) {
63
            return null;
64
        }
65
66
        $username = $user->getUsername();
67
        if (!$username) {
68
            throw new UnexpectedValueException(sprintf('The entity %s should have a username set to send a password reset email.', AbstractUser::class));
69
        }
70
        $user->setNewPasswordConfirmationToken($this->passwordHasher->hashPassword($user, $token = TokenGenerator::generateToken()));
71
        $user->plainNewPasswordConfirmationToken = $token;
72
        $user->setPasswordRequestedAt(new \DateTime());
73
74
        return $user;
75
    }
76
77
    public function passwordReset(string $username, string $token, string $newPassword): ?AbstractUser
78
    {
79
        $user = $this->userRepository->findOneWithPasswordResetToken($username);
80
        if (!$user) {
81
            return null;
82
        }
83
        $hasher = $this->passwordHasherFactory->getPasswordHasher($user);
84
        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

84
        if (!$hasher->verify(/** @scrutinizer ignore-type */ $user->getNewPasswordConfirmationToken(), $token)) {
Loading history...
85
            return null;
86
        }
87
88
        $user->setPlainPassword($newPassword);
89
        $user->setNewPasswordConfirmationToken(null);
90
        $user->setPasswordRequestedAt(null);
91
        $this->hashPassword($user);
92
93
        return $user;
94
    }
95
96
    public function processChanges(AbstractUser $user, ?AbstractUser $previousUser): void
97
    {
98
        $this->hashPassword($user);
99
        if (!$previousUser) {
100
            $user->setEmailAddressVerified($this->initialEmailVerifiedState);
101
            if (!$this->initialEmailVerifiedState && $this->verifyEmailOnRegister) {
102
                $user->setEmailAddressVerifyToken($this->passwordHasher->hashPassword($user, $token = TokenGenerator::generateToken()));
103
                $user->plainEmailAddressVerifyToken = $token;
104
            }
105
        }
106
107
        if ($previousUser) {
108
            if ($this->verifyEmailOnChange && $user->getEmailAddress() !== $previousUser->getEmailAddress()) {
109
                $user->setEmailAddressVerifyToken($this->passwordHasher->hashPassword($user, $token = TokenGenerator::generateToken()));
110
                $user->plainEmailAddressVerifyToken = $token;
111
            }
112
            if (($newEmail = $user->getNewEmailAddress()) !== $previousUser->getNewEmailAddress()) {
113
                if ($newEmail) {
114
                    $user->setNewEmailConfirmationToken($this->passwordHasher->hashPassword($user, $token = TokenGenerator::generateToken()));
115
                    $user->plainNewEmailConfirmationToken = $token;
116
                } else {
117
                    // invalidate any existing requests
118
                    $user->setNewEmailConfirmationToken(null);
119
                }
120
            }
121
        }
122
    }
123
124
    private function hashPassword(AbstractUser $entity): bool
125
    {
126
        if (!$entity->getPlainPassword()) {
127
            return false;
128
        }
129
        $encoded = $this->passwordHasher->hashPassword($entity, $entity->getPlainPassword());
130
        $entity->setPassword($encoded);
131
        $entity->eraseCredentials();
132
133
        return true;
134
    }
135
}
136