Completed
Pull Request — master (#2)
by Eugene
01:49
created

EmailUpdateConfirmation::getIvSize()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 4
ccs 2
cts 2
cp 1
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 0
crap 1
1
<?php
2
3
namespace Azine\EmailUpdateConfirmationBundle\Services;
4
5
use Azine\EmailUpdateConfirmationBundle\AzineEmailUpdateConfirmationEvents;
6
use Azine\EmailUpdateConfirmationBundle\EventListener\FlashListener;
7
use Azine\EmailUpdateConfirmationBundle\Mailer\EmailUpdateConfirmationMailerInterface;
8
use FOS\UserBundle\Event\UserEvent;
9
use FOS\UserBundle\Mailer\MailerInterface;
10
use FOS\UserBundle\Model\UserInterface;
11
use FOS\UserBundle\Util\TokenGenerator;
12
use Symfony\Bundle\FrameworkBundle\Routing\Router;
13
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
14
use Symfony\Component\HttpFoundation\Request;
15
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
16
use Symfony\Component\Validator\Constraints\Email;
17
use Symfony\Component\Validator\Validator\ValidatorInterface;
18
19
class EmailUpdateConfirmation implements EmailUpdateConfirmationInterface
20
{
21
    const EMAIL_CONFIRMED = 'email_confirmed';
22
23
    /**
24
     * @var EmailUpdateConfirmationMailerInterface
25
     */
26
    private $mailer;
27
28
    /**
29
     * @var Router
30
     */
31
    private $router;
32
33
    /**
34
     * @var TokenGenerator
35
     */
36
    private $tokenGenerator;
37
38
    /**
39
     * @var string
40
     */
41
    private $encryptionMode;
42
43
    /**
44
     * @var ValidatorInterface
45
     */
46
    private $validator;
47
48
    /**
49
     * @var string Route for confirmation link
50
     */
51
    private $confirmationRoute = 'user_update_email_confirm';
52
53
    /**
54
     * @var EventDispatcherInterface
55
     */
56
    private $eventDispatcher;
57
58
    /**
59
     * @var string $redirectRoute
60
     */
61
    private $redirectRoute;
62
63 6
    public function __construct(
64
        Router $router,
65
        TokenGenerator $tokenGenerator,
66
        EmailUpdateConfirmationMailerInterface $mailer,
67
        EventDispatcherInterface $eventDispatcher,
68
        ValidatorInterface $validator,
69
        $redirectRoute,
70
        $mode = null
71
    ) {
72 6
        $this->router = $router;
73 6
        $this->tokenGenerator = $tokenGenerator;
74 6
        $this->mailer = $mailer;
75 6
        $this->eventDispatcher = $eventDispatcher;
76 6
        $this->validator = $validator;
77 6
        $this->redirectRoute = $redirectRoute;
78
79 6
        if (!$mode) {
80
            $mode = openssl_get_cipher_methods(false)[0];
81
        }
82 6
        $this->encryptionMode = $mode;
83 6
    }
84
85
    /**
86
     * Get $mailer.
87
     *
88
     * @return MailerInterface
89
     */
90
    public function getMailer()
91
    {
92
        return $this->mailer;
93
    }
94
95
    /**
96
     * Generate new confirmation link for new email based on user confirmation
97
     * token and hashed new user email.
98
     *
99
     * @param Request $request
100
     *
101
     * @return string
102
     */
103
    public function generateConfirmationLink(Request $request, UserInterface $user, $email)
104
    {
105
        if (!$user->getConfirmationToken()) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $user->getConfirmationToken() of type string|null is loosely compared to false; this is ambiguous if the string can be empty. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
106
            $user->setConfirmationToken(
107
                $this->tokenGenerator->generateToken()
108
            );
109
        }
110
111
        $encryptedEmail = $this->encryptEmailValue($user->getConfirmationToken(), $email);
112
113
        $confirmationParams = array('token' => $user->getConfirmationToken(), 'target' => $encryptedEmail, 'redirectRoute' => $this->redirectRoute);
114
115
        $event = new UserEvent($user, $request);
116
117
        $this->eventDispatcher->dispatch(AzineEmailUpdateConfirmationEvents::EMAIL_UPDATE_INITIALIZE, $event);
118
119
        return $this->router->generate(
120
            $this->confirmationRoute,
121
            $confirmationParams,
122
            UrlGeneratorInterface::ABSOLUTE_URL
123
        );
124
    }
125
126
    /**
127
     * Fetch email value from hashed part of confirmation link.
128
     * @param UserInterface $user
129
     * @param string $hashedEmail
130
     *
131
     * @return string Encrypted email
132
     */
133 1
    public function fetchEncryptedEmailFromConfirmationLink($user, $hashedEmail)
134
    {
135
        //replace spaces with plus sign from hash, which could be replaced in url
136 1
        $hashedEmail = str_replace(' ', '+', $hashedEmail);
137
138 1
        $email = $this->decryptEmailValue($user->getConfirmationToken(), $hashedEmail);
139
140 1
        return $email;
141
    }
142
143
    /**
144
     * Get token which indicates that email was confirmed.
145
     *
146
     * @return string
147
     */
148
    public function getEmailConfirmedToken()
149
    {
150
        return base64_encode(self::EMAIL_CONFIRMED);
151
    }
152
153
    /**
154
     * Return IV size.
155
     *
156
     * @return int
157
     */
158 3
    protected function getIvSize()
159
    {
160 3
        return openssl_cipher_iv_length($this->encryptionMode);
161
    }
162
163
    /**
164
     * Encrypt email value with specified user confirmation token.
165
     * @param string $userConfirmationToken
166
     * @param string $email
167
     *
168
     * @return string Encrypted email
169
     */
170 5
    public function encryptEmailValue($userConfirmationToken, $email)
171
    {
172 5
        if (!$userConfirmationToken || !is_string($userConfirmationToken)) {
173 1
            throw new \InvalidArgumentException(
174 1
                'Invalid user confirmation token value.'
175
            );
176
        }
177
178 4
        if (!is_string($email)) {
179 1
            throw new \InvalidArgumentException(
180
                'Email to be encrypted should a string. '
181 1
                .gettype($email).' given.'
182
            );
183
        }
184
185 3
        $iv = openssl_random_pseudo_bytes($this->getIvSize());
186
187 3
        $encryptedEmail = openssl_encrypt(
188 3
            $email,
189 3
            $this->encryptionMode,
190 3
            pack('H*', hash('sha256', $userConfirmationToken)),
191 3
            0,
192 3
            $iv
193
        );
194
195 3
        $encryptedEmail = base64_encode($iv.$encryptedEmail);
196
197 3
        return $encryptedEmail;
198
    }
199
200
    /**
201
     * Decrypt email value with specified user confirmation token.
202
     * @param string $userConfirmationToken
203
     * @param string $encryptedEmail
204
     *
205
     * @return string Decrypted email
206
     */
207 4
    public function decryptEmailValue($userConfirmationToken, $encryptedEmail)
208
    {
209 4
        if (!$userConfirmationToken || !is_string($userConfirmationToken)) {
210 1
            throw new \InvalidArgumentException(
211 1
                'Invalid user confirmation token value.'
212
            );
213
        }
214
215 3
        $b64DecodedEmailHash = base64_decode($encryptedEmail);
216 3
        $ivSize = $this->getIvSize();
217
218
        // Select IV part from encrypted value
219 3
        $iv = substr($b64DecodedEmailHash, 0, $ivSize);
220
221
        // Select email part from encrypted value
222 3
        $preparedEncryptedEmail = substr($b64DecodedEmailHash, $ivSize);
223
224 3
        $decryptedEmail = openssl_decrypt(
225 3
            $preparedEncryptedEmail,
226 3
            $this->encryptionMode,
227 3
            pack('H*', hash('sha256', $userConfirmationToken)),
228 3
            0,
229 3
            $iv
230
        );
231
232
        // Trim decrypted email from nul byte before return
233 3
        $email = rtrim($decryptedEmail, "\0");
234
235
        /** @var ConstraintViolationList $violationList */
236 3
        $violationList = $this->validator->validate($email, new Email());
237 3
        if ($violationList->count() > 0) {
238 1
            throw new \InvalidArgumentException('Wrong email format was provided for decryptEmailValue function');
239
        }
240
241 2
        return $email;
242
    }
243
}
244