Completed
Pull Request — master (#2)
by Eugene
02:20
created

EmailUpdateConfirmation::encryptEmailValue()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 22

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 12

Importance

Changes 0
Metric Value
dl 0
loc 22
ccs 0
cts 18
cp 0
rs 9.568
c 0
b 0
f 0
cc 3
nc 2
nop 2
crap 12
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
    private $mailer;
24
    private $router;
25
    private $tokenGenerator;
26
27
    /**
28
     * @var string
29
     */
30
    private $encryptionMode;
31
32
    /**
33
     * @var ValidatorInterface
34
     */
35
    private $validator;
36
37
    /**
38
     * @var string Route for confirmation link
39
     */
40
    private $confirmationRoute = 'user_update_email_confirm';
41
    private $eventDispatcher;
42
    private $redirectRoute;
43
44
    public function __construct(
45
        Router $router,
46
        TokenGenerator $tokenGenerator,
47
        EmailUpdateConfirmationMailerInterface $mailer,
48
        EventDispatcherInterface $eventDispatcher,
49
        ValidatorInterface $validator,
50
        $redirectRoute,
51
        $mode = null
52
    ) {
53
        $this->router = $router;
54
        $this->tokenGenerator = $tokenGenerator;
55
        $this->mailer = $mailer;
56
        $this->eventDispatcher = $eventDispatcher;
57
        $this->validator = $validator;
58
        $this->redirectRoute = $redirectRoute;
59
60
        if (!$mode) {
61
            $mode = openssl_get_cipher_methods(false)[0];
62
        }
63
        $this->encryptionMode = $mode;
64
    }
65
66
    /**
67
     * Get $mailer.
68
     *
69
     * @return MailerInterface
70
     */
71
    public function getMailer()
72
    {
73
        return $this->mailer;
74
    }
75
76
    /**
77
     * Generate new confirmation link for new email based on user confirmation
78
     * token and hashed new user email.
79
     *
80
     * @param Request $request
81
     *
82
     * @return string
83
     */
84
    public function generateConfirmationLink(Request $request, UserInterface $user, $email)
85
    {
86
        if (!is_string($email)) {
87
            throw new \InvalidArgumentException(
88
                'Email to be encrypted should a string. '
89
                .gettype($email).' given.'
90
            );
91
        }
92
93
        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...
94
            $user->setConfirmationToken(
95
                $this->tokenGenerator->generateToken()
96
            );
97
        }
98
99
        $encryptedEmail = $this->encryptEmailValue($user->getConfirmationToken(), $email);
100
101
        $confirmationParams = array('token' => $user->getConfirmationToken(), 'target' => $encryptedEmail, 'redirectRoute' => $this->redirectRoute);
102
103
        $event = new UserEvent($user, $request);
104
105
        $this->eventDispatcher->dispatch(AzineEmailUpdateConfirmationEvents::EMAIL_UPDATE_INITIALIZE, $event);
106
107
        return $this->router->generate(
108
            $this->confirmationRoute,
109
            $confirmationParams,
110
            UrlGeneratorInterface::ABSOLUTE_URL
111
        );
112
    }
113
114
    /**
115
     * Fetch email value from hashed part of confirmation link.
116
     * @param UserInterface $user
117
     * @param string $hashedEmail
118
     *
119
     * @return string Encrypted email
120
     */
121
    public function fetchEncryptedEmailFromConfirmationLink($user, $hashedEmail)
122
    {
123
        //replace spaces with plus sign from hash, which could be replaced in url
124
        $hashedEmail = str_replace(' ', '+', $hashedEmail);
125
126
        $email = $this->decryptEmailValue($user->getConfirmationToken(), $hashedEmail);
127
128
        return $email;
129
    }
130
131
    /**
132
     * Get token which indicates that email was confirmed.
133
     *
134
     * @return string
135
     */
136
    public function getEmailConfirmedToken()
137
    {
138
        return base64_encode(self::EMAIL_CONFIRMED);
139
    }
140
141
    /**
142
     * Return IV size.
143
     *
144
     * @return int
145
     */
146
    protected function getIvSize()
147
    {
148
        return openssl_cipher_iv_length($this->encryptionMode);
149
    }
150
151
    /**
152
     * Encrypt email value with specified user confirmation token.
153
     *
154
     * @return string Encrypted email
155
     */
156
    public function encryptEmailValue($userConfirmationToken, $email)
157
    {
158
        if (!$userConfirmationToken || !is_string($userConfirmationToken)) {
159
            throw new \InvalidArgumentException(
160
                'Invalid user confirmation token value.'
161
            );
162
        }
163
164
        $iv = openssl_random_pseudo_bytes($this->getIvSize());
165
166
        $encryptedEmail = openssl_encrypt(
167
            $email,
168
            $this->encryptionMode,
169
            $userConfirmationToken,
170
            0,
171
            $iv
172
        );
173
174
        $encryptedEmail = base64_encode($iv.$encryptedEmail);
175
176
        return $encryptedEmail;
177
    }
178
179
    /**
180
     * Decrypt email value with specified user confirmation token.
181
     * @param string $userConfirmationToken
182
     * @param string $encryptedEmail
183
     *
184
     * @return string Decrypted email
185
     */
186
    public function decryptEmailValue($userConfirmationToken, $encryptedEmail)
187
    {
188
        $b64DecodedEmailHash = base64_decode($encryptedEmail);
189
        $ivSize = $this->getIvSize();
190
191
        // Select IV part from encrypted value
192
        $iv = substr($b64DecodedEmailHash, 0, $ivSize);
193
194
        // Select email part from encrypted value
195
        $preparedEncryptedEmail = substr($b64DecodedEmailHash, $ivSize);
196
197
        $decryptedEmail = openssl_decrypt(
198
            $preparedEncryptedEmail,
199
            $this->encryptionMode,
200
            $userConfirmationToken,
201
            0,
202
            $iv
203
        );
204
205
        // Trim decrypted email from nul byte before return
206
        $email = rtrim($decryptedEmail, "\0");
207
208
        /** @var ConstraintViolationList $violationList */
209
        $violationList = $this->validator->validate($email, new Email());
210
        if ($violationList->count() > 0) {
211
            throw new \InvalidArgumentException('Wrong email format was provided for decryptEmailValue function');
212
        }
213
214
        return $email;
215
    }
216
}
217