Passed
Push — master ( 0716b8...6a0d02 )
by Jan
04:25
created

PasswordResetManager::setNewPassword()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 32
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 4
eloc 13
nc 4
nop 3
dl 0
loc 32
rs 9.8333
c 1
b 0
f 0
1
<?php
2
/**
3
 * This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
4
 *
5
 * Copyright (C) 2019 Jan Böhmer (https://github.com/jbtronics)
6
 *
7
 * This program is free software; you can redistribute it and/or
8
 * modify it under the terms of the GNU General Public License
9
 * as published by the Free Software Foundation; either version 2
10
 * of the License, or (at your option) any later version.
11
 *
12
 * This program is distributed in the hope that it will be useful,
13
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15
 * GNU General Public License for more details.
16
 *
17
 * You should have received a copy of the GNU General Public License
18
 * along with this program; if not, write to the Free Software
19
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
20
 */
21
22
namespace App\Services;
23
24
25
use App\Entity\UserSystem\User;
26
use Doctrine\ORM\EntityManagerInterface;
27
use Symfony\Bridge\Twig\Mime\TemplatedEmail;
28
use Symfony\Component\Mailer\MailerInterface;
29
use Symfony\Component\Mime\Address;
30
use Symfony\Component\Security\Core\Encoder\EncoderFactoryInterface;
31
use Symfony\Component\Security\Core\Encoder\PasswordEncoderInterface;
32
use Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface;
33
use Symfony\Contracts\Translation\TranslatorInterface;
34
35
class PasswordResetManager
36
{
37
    protected $mailer;
38
    protected $em;
39
    protected $passwordEncoder;
40
    protected $translator;
41
    protected $userPasswordEncoder;
42
43
    public function __construct(MailerInterface $mailer, EntityManagerInterface $em,
44
                                TranslatorInterface $translator, UserPasswordEncoderInterface $userPasswordEncoder,
45
                                EncoderFactoryInterface $encoderFactory)
46
    {
47
        $this->em = $em;
48
        $this->mailer = $mailer;
49
        /** @var PasswordEncoderInterface passwordEncoder */
50
        $this->passwordEncoder = $encoderFactory->getEncoder(User::class);
51
        $this->translator = $translator;
52
        $this->userPasswordEncoder = $userPasswordEncoder;
53
    }
54
55
    public function request(string $name_or_email) : void
56
    {
57
        $repo = $this->em->getRepository(User::class);
58
59
        //Try to find a user by the given string
60
        $user = $repo->findByEmailOrName($name_or_email);
61
        //Do nothing if no user was found
62
        if ($user === null) {
63
            return;
64
        }
65
66
        $unencrypted_token = md5(random_bytes(32));
67
        $user->setPwResetToken($this->passwordEncoder->encodePassword($unencrypted_token, null));
68
69
        //Determine the expiration datetime of
70
        $expiration_date = new \DateTime();
71
        $expiration_date->add(date_interval_create_from_date_string('1 day'));
72
        $user->setPwResetExpires($expiration_date);
73
74
        if (!empty($user->getEmail())) {
75
            $address = new Address($user->getEmail(), $user->getFullName());
76
            $mail = new TemplatedEmail();
77
            $mail->to($address);
78
            $mail->subject($this->translator->trans('pw_reset.email.subject'));
79
            $mail->htmlTemplate("mail/pw_reset.html.twig");
80
            $mail->context([
81
                'expiration_date' => $expiration_date,
82
                'token' => $unencrypted_token,
83
                'user' => $user
84
            ]);
85
86
            //Send email
87
            $this->mailer->send($mail);
88
        }
89
90
        //Save changes to DB
91
        $this->em->flush();
92
    }
93
94
    /**
95
     * Sets the new password of the user with the given name, if the token is valid.
96
     * @param string $user The name of the user, which password should be reset
97
     * @param string $token The token that should be used to reset the password
98
     * @param string $new_password The new password that should be applied to user
99
     * @return bool Returns true, if the new password was applied. False, if either the username is unknown or the
100
     * token is invalid or expired.
101
     */
102
    public function setNewPassword(string $user, string $token, string $new_password) : bool
103
    {
104
        //Try to find the user
105
        $repo = $this->em->getRepository(User::class);
106
        /** @var User $user */
107
        $user = $repo->findOneBy(['name' => $user]);
108
109
        //If no user matching the name, show an error message
110
        if ($user === null) {
111
            return false;
112
        }
113
114
        //Check if token is expired yet
115
        if ($user->getPwResetExpires() < new \DateTime()) {
116
            return false;
117
        }
118
119
        //Check if token is valid
120
        if (!$this->passwordEncoder->isPasswordValid($user->getPwResetToken(), $token, null)) {
121
            return false;
122
        }
123
124
        //When everything was valid, apply the new password
125
        $user->setPassword($this->userPasswordEncoder->encodePassword($user, $new_password));
126
127
        //Remove token
128
        $user->setPwResetToken(null);
129
        $user->setPwResetExpires(new \DateTime());
130
131
        //Save to DB
132
        $this->em->flush();
133
        return true;
134
    }
135
}