Completed
Push — master ( 93b50d...02fb34 )
by Dominik
10:08 queued 03:07
created

EmailUpdateConfirmation::encryptEmailValue()   A

Complexity

Conditions 4
Paths 3

Size

Total Lines 29

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 16
CRAP Score 4

Importance

Changes 0
Metric Value
dl 0
loc 29
ccs 16
cts 16
cp 1
rs 9.456
c 0
b 0
f 0
cc 4
nc 3
nop 2
crap 4
1
<?php
2
3
namespace Azine\EmailUpdateConfirmationBundle\Services;
4
5
use Azine\EmailUpdateConfirmationBundle\AzineEmailUpdateConfirmationEvents;
6
use Azine\EmailUpdateConfirmationBundle\Mailer\EmailUpdateConfirmationMailerInterface;
7
use FOS\UserBundle\Event\UserEvent;
8
use FOS\UserBundle\Mailer\MailerInterface;
9
use FOS\UserBundle\Model\UserInterface;
10
use FOS\UserBundle\Util\TokenGenerator;
11
use Symfony\Bundle\FrameworkBundle\Routing\Router;
12
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
13
use Symfony\Component\HttpFoundation\Request;
14
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
15
use Symfony\Component\Validator\Constraints\Email;
16
use Symfony\Component\Validator\Validator\ValidatorInterface;
17
18
class EmailUpdateConfirmation implements EmailUpdateConfirmationInterface
19
{
20
    const EMAIL_CONFIRMED = 'email_confirmed';
21
22
    /**
23
     * @var EmailUpdateConfirmationMailerInterface
24
     */
25
    private $mailer;
26
27
    /**
28
     * @var Router
29
     */
30
    private $router;
31
32
    /**
33
     * @var TokenGenerator
34
     */
35
    private $tokenGenerator;
36
37
    /**
38
     * @var string
39
     */
40
    private $encryptionMode;
41
42
    /**
43
     * @var ValidatorInterface
44
     */
45
    private $validator;
46
47
    /**
48
     * @var string Route for confirmation link
49
     */
50
    private $confirmationRoute = 'user_update_email_confirm';
51
52
    /**
53
     * @var EventDispatcherInterface
54
     */
55
    private $eventDispatcher;
56
57
    /**
58
     * @var string
59
     */
60
    private $redirectRoute;
61
62 6
    public function __construct(
63
        Router $router,
64
        TokenGenerator $tokenGenerator,
65
        EmailUpdateConfirmationMailerInterface $mailer,
66
        EventDispatcherInterface $eventDispatcher,
67
        ValidatorInterface $validator,
68
        $redirectRoute,
69
        $mode = null
70
    ) {
71 6
        $this->router = $router;
72 6
        $this->tokenGenerator = $tokenGenerator;
73 6
        $this->mailer = $mailer;
74 6
        $this->eventDispatcher = $eventDispatcher;
75 6
        $this->validator = $validator;
76 6
        $this->redirectRoute = $redirectRoute;
77
78 6
        if (!$mode) {
79
            $mode = openssl_get_cipher_methods(false)[0];
80
        }
81 6
        $this->encryptionMode = $mode;
82 6
    }
83
84
    /**
85
     * Get $mailer.
86
     *
87
     * @return MailerInterface
88
     */
89
    public function getMailer()
90
    {
91
        return $this->mailer;
92
    }
93
94
    /**
95
     * Generate new confirmation link for new email based on user confirmation
96
     * token and hashed new user email.
97
     *
98
     * @param Request $request
99
     *
100
     * @return string
101
     */
102
    public function generateConfirmationLink(Request $request, UserInterface $user, $email)
103
    {
104
        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...
105
            $user->setConfirmationToken(
106
                $this->tokenGenerator->generateToken()
107
            );
108
        }
109
110
        $encryptedEmail = $this->encryptEmailValue($user->getConfirmationToken(), $email);
111
112
        $confirmationParams = array('token' => $user->getConfirmationToken(), 'target' => $encryptedEmail, 'redirectRoute' => $this->redirectRoute);
113
114
        $event = new UserEvent($user, $request);
115
116
        $this->eventDispatcher->dispatch(AzineEmailUpdateConfirmationEvents::EMAIL_UPDATE_INITIALIZE, $event);
117
118
        return $this->router->generate(
119
            $this->confirmationRoute,
120
            $confirmationParams,
121
            UrlGeneratorInterface::ABSOLUTE_URL
122
        );
123
    }
124
125
    /**
126
     * Fetch email value from hashed part of confirmation link.
127
     *
128
     * @param UserInterface $user
129
     * @param string        $hashedEmail
130
     *
131
     * @return string Encrypted email
132
     */
133 1
    public function fetchEncryptedEmailFromConfirmationLink(UserInterface $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
     *
166
     * @param string $userConfirmationToken
167
     * @param string $email
168
     *
169
     * @return string Encrypted email
170
     */
171 5
    public function encryptEmailValue($userConfirmationToken, $email)
172
    {
173 5
        if (!$userConfirmationToken || !is_string($userConfirmationToken)) {
174 1
            throw new \InvalidArgumentException(
175 1
                'Invalid user confirmation token value.'
176
            );
177
        }
178
179 4
        if (!is_string($email)) {
180 1
            throw new \InvalidArgumentException(
181
                'Email to be encrypted should a string. '
182 1
                .gettype($email).' given.'
183
            );
184
        }
185
186 3
        $iv = openssl_random_pseudo_bytes($this->getIvSize());
187
188 3
        $encryptedEmail = openssl_encrypt(
189 3
            $email,
190 3
            $this->encryptionMode,
191 3
            pack('H*', hash('sha256', $userConfirmationToken)),
192 3
            0,
193 3
            $iv
194
        );
195
196 3
        $encryptedEmail = base64_encode($iv.$encryptedEmail);
197
198 3
        return $encryptedEmail;
199
    }
200
201
    /**
202
     * Decrypt email value with specified user confirmation token.
203
     *
204
     * @param string $userConfirmationToken
205
     * @param string $encryptedEmail
206
     *
207
     * @return string Decrypted email
208
     */
209 4
    public function decryptEmailValue($userConfirmationToken, $encryptedEmail)
210
    {
211 4
        if (!$userConfirmationToken || !is_string($userConfirmationToken)) {
212 1
            throw new \InvalidArgumentException(
213 1
                'Invalid user confirmation token value.'
214
            );
215
        }
216
217 3
        $b64DecodedEmailHash = base64_decode($encryptedEmail);
218 3
        $ivSize = $this->getIvSize();
219
220
        // Select IV part from encrypted value
221 3
        $iv = substr($b64DecodedEmailHash, 0, $ivSize);
222
223
        // Select email part from encrypted value
224 3
        $preparedEncryptedEmail = substr($b64DecodedEmailHash, $ivSize);
225
226 3
        $decryptedEmail = openssl_decrypt(
227 3
            $preparedEncryptedEmail,
228 3
            $this->encryptionMode,
229 3
            pack('H*', hash('sha256', $userConfirmationToken)),
230 3
            0,
231 3
            $iv
232
        );
233
234
        // Trim decrypted email from nul byte before return
235 3
        $email = rtrim($decryptedEmail, "\0");
236
237
        /** @var ConstraintViolationList $violationList */
238 3
        $violationList = $this->validator->validate($email, new Email());
239 3
        if ($violationList->count() > 0) {
240 1
            throw new \InvalidArgumentException('Wrong email format was provided for decryptEmailValue function');
241
        }
242
243 2
        return $email;
244
    }
245
}
246