Completed
Push — develop ( 9664f4...8505c4 )
by Abdelrahman
06:05
created

EmailVerificationBroker::verify()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 21
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 7
c 0
b 0
f 0
nc 2
nop 2
dl 0
loc 21
rs 9.3142
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Rinvex\Fort\Services;
6
7
use Closure;
8
use Illuminate\Support\Arr;
9
use Illuminate\Support\Str;
10
use UnexpectedValueException;
11
use Illuminate\Contracts\Auth\UserProvider;
12
use Rinvex\Fort\Contracts\CanVerifyEmailContract;
13
use Rinvex\Fort\Contracts\EmailVerificationBrokerContract;
14
15
class EmailVerificationBroker implements EmailVerificationBrokerContract
16
{
17
    /**
18
     * The application key.
19
     *
20
     * @var string
21
     */
22
    protected $key;
23
24
    /**
25
     * The user provider implementation.
26
     *
27
     * @var \Illuminate\Contracts\Auth\UserProvider
28
     */
29
    protected $users;
30
31
    /**
32
     * The number of minutes that the reset token should be considered valid.
33
     *
34
     * @var int
35
     */
36
    protected $expiration;
37
38
    /**
39
     * Create a new verification broker instance.
40
     *
41
     * @param \Illuminate\Contracts\Auth\UserProvider $users
42
     * @param string                                  $key
43
     * @param int                                     $expiration
44
     */
45
    public function __construct(UserProvider $users, $key, $expiration)
46
    {
47
        $this->key = $key;
48
        $this->users = $users;
49
        $this->expiration = $expiration;
50
    }
51
52
    /**
53
     * {@inheritdoc}
54
     */
55
    public function sendVerificationLink(array $credentials): string
56
    {
57
        // First we will check to see if we found a user at the given credentials and
58
        // if we did not we will redirect back to this current URI with a piece of
59
        // "flash" data in the session to indicate to the developers the errors.
60
        if (is_null($user = $this->getUser($credentials))) {
61
            return static::INVALID_USER;
62
        }
63
64
        $expiration = now()->addMinutes($this->expiration)->timestamp;
65
66
        // Once we have the verification token, we are ready to send the message out to
67
        // this user with a link to verify their email. We will then redirect back to
68
        // the current URI having nothing set in the session to indicate any errors
69
        $user->sendEmailVerificationNotification($this->createToken($user, $expiration), $expiration);
70
71
        return static::LINK_SENT;
72
    }
73
74
    /**
75
     * {@inheritdoc}
76
     */
77
    public function verify(array $credentials, Closure $callback)
78
    {
79
        // If the responses from the validate method is not a user instance, we will
80
        // assume that it is a redirect and simply return it from this method and
81
        // the user is properly redirected having an error message on the post.
82
        $user = $this->validateVerification($credentials);
83
84
        if (! $user instanceof CanVerifyEmailContract) {
85
            return $user;
86
        }
87
88
        // Once the email has been verified, we'll call the given
89
        // callback, then we'll delete the token and return.
90
        // in their persistent storage.
91
        $callback($user);
92
93
        // Fire the email verification success event
94
        event('rinvex.fort.emailverification.success', [$user]);
95
96
        return static::EMAIL_VERIFIED;
97
    }
98
99
    /**
100
     * Get the user for the given credentials.
101
     *
102
     * @param array $credentials
103
     *
104
     * @throws \UnexpectedValueException
105
     *
106
     * @return \Rinvex\Fort\Contracts\CanVerifyEmailContract
107
     */
108
    public function getUser(array $credentials): CanVerifyEmailContract
109
    {
110
        $user = $this->users->retrieveByCredentials(Arr::only($credentials, ['email']));
111
112
        if ($user && ! $user instanceof CanVerifyEmailContract) {
113
            throw new UnexpectedValueException('User must implement CanVerifyEmailContract interface.');
114
        }
115
116
        return $user;
117
    }
118
119
    /**
120
     * Create a new email verification token for the given user.
121
     *
122
     * @param \Rinvex\Fort\Contracts\CanVerifyEmailContract $user
123
     * @param int                                           $expiration
124
     *
125
     * @return string
126
     */
127
    public function createToken(CanVerifyEmailContract $user, $expiration): string
128
    {
129
        $payload = $this->buildPayload($user, $user->getEmailForVerification(), $expiration);
130
131
        return hash_hmac('sha256', $payload, $this->getKey());
132
    }
133
134
    /**
135
     * Validate the given email verification token.
136
     *
137
     * @param \Rinvex\Fort\Contracts\CanVerifyEmailContract $user
138
     * @param array                                         $credentials
139
     *
140
     * @return bool
141
     */
142
    public function validateToken(CanVerifyEmailContract $user, array $credentials): bool
143
    {
144
        $payload = $this->buildPayload($user, $credentials['email'], $credentials['expiration']);
145
146
        return hash_equals($credentials['token'], hash_hmac('sha256', $payload, $this->getKey()));
147
    }
148
149
    /**
150
     * Validate the given expiration timestamp.
151
     *
152
     * @param int $expiration
153
     *
154
     * @return bool
155
     */
156
    public function validateTimestamp($expiration): bool
157
    {
158
        return now()->createFromTimestamp($expiration)->isFuture();
159
    }
160
161
    /**
162
     * Return the application key.
163
     *
164
     * @return string
165
     */
166
    public function getKey(): string
167
    {
168
        if (Str::startsWith($this->key, 'base64:')) {
169
            return base64_decode(mb_substr($this->key, 7));
170
        }
171
172
        return $this->key;
173
    }
174
175
    /**
176
     * Returns the payload string containing.
177
     *
178
     * @param \Rinvex\Fort\Contracts\CanVerifyEmailContract $user
179
     * @param string                                        $email
180
     * @param int                                           $expiration
181
     *
182
     * @return string
183
     */
184
    protected function buildPayload(CanVerifyEmailContract $user, $email, $expiration): string
185
    {
186
        return implode(';', [
187
            $email,
188
            $expiration,
189
            $user->getKey(),
190
            $user->password,
0 ignored issues
show
Bug introduced by
Accessing password on the interface Rinvex\Fort\Contracts\CanVerifyEmailContract suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
191
        ]);
192
    }
193
194
    /**
195
     * Validate an email verification for the given credentials.
196
     *
197
     * @param array $credentials
198
     *
199
     * @return \Rinvex\Fort\Contracts\CanVerifyEmailContract|string
200
     */
201
    protected function validateVerification(array $credentials)
202
    {
203
        if (is_null($user = $this->getUser($credentials))) {
204
            return static::INVALID_USER;
205
        }
206
207
        if (! $this->validateToken($user, $credentials)) {
208
            return static::INVALID_TOKEN;
209
        }
210
211
        if (! $this->validateTimestamp($credentials['expiration'])) {
212
            return static::EXPIRED_TOKEN;
213
        }
214
215
        return $user;
216
    }
217
}
218