Completed
Push — master ( 425779...b6e961 )
by Abdelrahman
08:30
created

EmailVerificationBroker::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 4
nc 1
nop 3
dl 0
loc 6
rs 9.4285
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Rinvex\Fort\Services;
6
7
use Closure;
8
use Carbon\Carbon;
9
use Illuminate\Support\Arr;
10
use Illuminate\Support\Str;
11
use UnexpectedValueException;
12
use Illuminate\Contracts\Auth\UserProvider;
13
use Rinvex\Fort\Contracts\CanVerifyEmailContract;
14
use Rinvex\Fort\Contracts\EmailVerificationBrokerContract;
15
16
class EmailVerificationBroker implements EmailVerificationBrokerContract
17
{
18
    /**
19
     * The application key.
20
     *
21
     * @var string
22
     */
23
    protected $key;
24
25
    /**
26
     * The user provider implementation.
27
     *
28
     * @var \Illuminate\Contracts\Auth\UserProvider
29
     */
30
    protected $users;
31
32
    /**
33
     * The number of minutes that the reset token should be considered valid.
34
     *
35
     * @var int
36
     */
37
    protected $expiration;
38
39
    /**
40
     * Create a new verification broker instance.
41
     *
42
     * @param \Illuminate\Contracts\Auth\UserProvider $users
43
     * @param string                                  $key
44
     * @param int                                     $expiration
45
     */
46
    public function __construct(UserProvider $users, $key, $expiration)
47
    {
48
        $this->key = $key;
49
        $this->users = $users;
50
        $this->expiration = $expiration;
51
    }
52
53
    /**
54
     * {@inheritdoc}
55
     */
56
    public function sendVerificationLink(array $credentials)
57
    {
58
        // First we will check to see if we found a user at the given credentials and
59
        // if we did not we will redirect back to this current URI with a piece of
60
        // "flash" data in the session to indicate to the developers the errors.
61
        if (is_null($user = $this->getUser($credentials))) {
62
            return static::INVALID_USER;
63
        }
64
65
        $expiration = Carbon::now()->addMinutes($this->expiration)->timestamp;
66
67
        // Once we have the verification token, we are ready to send the message out to
68
        // this user with a link to verify their email. We will then redirect back to
69
        // the current URI having nothing set in the session to indicate any errors
70
        $user->sendEmailVerificationNotification($this->createToken($user, $expiration), $expiration);
0 ignored issues
show
Documentation introduced by
$user is of type object<Illuminate\Contracts\Auth\Authenticatable>, but the function expects a object<Rinvex\Fort\Contr...CanVerifyEmailContract>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
71
72
        return static::LINK_SENT;
73
    }
74
75
    /**
76
     * {@inheritdoc}
77
     */
78
    public function verify(array $credentials, Closure $callback)
79
    {
80
        // If the responses from the validate method is not a user instance, we will
81
        // assume that it is a redirect and simply return it from this method and
82
        // the user is properly redirected having an error message on the post.
83
        $user = $this->validateVerification($credentials);
84
85
        if (! $user instanceof CanVerifyEmailContract) {
86
            return $user;
87
        }
88
89
        // Fire the email verification start event
90
        event('rinvex.fort.emailverification.start', [$user]);
91
92
        // Once the email has been verified, we'll call the given
93
        // callback, then we'll delete the token and return.
94
        // in their persistent storage.
95
        $callback($user);
96
97
        // Fire the email verification success event
98
        event('rinvex.fort.emailverification.success', [$user]);
99
100
        return static::EMAIL_VERIFIED;
101
    }
102
103
    /**
104
     * Get the user for the given credentials.
105
     *
106
     * @param array $credentials
107
     *
108
     * @throws \UnexpectedValueException
109
     *
110
     * @return \Rinvex\Fort\Contracts\CanVerifyEmailContract
0 ignored issues
show
Documentation introduced by
Should the return type not be \Illuminate\Contracts\Auth\Authenticatable|null?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
111
     */
112
    public function getUser(array $credentials)
113
    {
114
        $user = $this->users->retrieveByCredentials(Arr::only($credentials, ['email']));
115
116
        if ($user && ! $user instanceof CanVerifyEmailContract) {
117
            throw new UnexpectedValueException('User must implement CanVerifyEmailContract interface.');
118
        }
119
120
        return $user;
121
    }
122
123
    /**
124
     * Create a new email verification token for the given user.
125
     *
126
     * @param \Rinvex\Fort\Contracts\CanVerifyEmailContract $user
127
     * @param int                                           $expiration
128
     *
129
     * @return string
130
     */
131
    public function createToken(CanVerifyEmailContract $user, $expiration)
132
    {
133
        $payload = $this->buildPayload($user, $user->getEmailForVerification(), $expiration);
134
135
        return hash_hmac('sha256', $payload, $this->getKey());
136
    }
137
138
    /**
139
     * Validate the given email verification token.
140
     *
141
     * @param \Rinvex\Fort\Contracts\CanVerifyEmailContract $user
142
     * @param array                                         $credentials
143
     *
144
     * @return bool
145
     */
146
    public function validateToken(CanVerifyEmailContract $user, array $credentials)
147
    {
148
        $payload = $this->buildPayload($user, $credentials['email'], $credentials['expiration']);
149
150
        return hash_equals($credentials['token'], hash_hmac('sha256', $payload, $this->getKey()));
151
    }
152
153
    /**
154
     * Validate the given expiration timestamp.
155
     *
156
     * @param int $expiration
157
     *
158
     * @return bool
159
     */
160
    public function validateTimestamp($expiration)
161
    {
162
        return Carbon::createFromTimestamp($expiration)->isFuture();
163
    }
164
165
    /**
166
     * Return the application key.
167
     *
168
     * @return string
169
     */
170
    public function getKey()
171
    {
172
        if (Str::startsWith($this->key, 'base64:')) {
173
            return base64_decode(mb_substr($this->key, 7));
174
        }
175
176
        return $this->key;
177
    }
178
179
    /**
180
     * Returns the payload string containing.
181
     *
182
     * @param \Rinvex\Fort\Contracts\CanVerifyEmailContract $user
183
     * @param string                                        $email
184
     * @param int                                           $expiration
185
     *
186
     * @return string
187
     */
188
    protected function buildPayload(CanVerifyEmailContract $user, $email, $expiration)
189
    {
190
        return implode(';', [
191
            $email,
192
            $expiration,
193
            $user->getKey(),
194
            $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...
195
        ]);
196
    }
197
198
    /**
199
     * Validate an email verification for the given credentials.
200
     *
201
     * @param array $credentials
202
     *
203
     * @return \Rinvex\Fort\Contracts\CanVerifyEmailContract|string
0 ignored issues
show
Documentation introduced by
Should the return type not be string|\Illuminate\Contracts\Auth\Authenticatable?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
204
     */
205
    protected function validateVerification(array $credentials)
206
    {
207
        if (is_null($user = $this->getUser($credentials))) {
208
            return static::INVALID_USER;
209
        }
210
211
        if (! $this->validateToken($user, $credentials)) {
0 ignored issues
show
Documentation introduced by
$user is of type object<Illuminate\Contracts\Auth\Authenticatable>, but the function expects a object<Rinvex\Fort\Contr...CanVerifyEmailContract>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
212
            return static::INVALID_TOKEN;
213
        }
214
215
        if (! $this->validateTimestamp($credentials['expiration'])) {
216
            return static::EXPIRED_TOKEN;
217
        }
218
219
        return $user;
220
    }
221
}
222