Completed
Push — master ( 89f500...10eccf )
by Abdelrahman
09:49 queued 08:40
created

PasswordResetBroker::createToken()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

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

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...
74
75
        return static::RESET_LINK_SENT;
76
    }
77
78
    /**
79
     * Reset the password for the given token.
80
     *
81
     * @param array    $credentials
82
     * @param \Closure $callback
83
     *
84
     * @return mixed
0 ignored issues
show
Documentation introduced by
Consider making the return type a bit more specific; maybe use string|\Illuminate\Contracts\Auth\Authenticatable.

This check looks for the generic type array as a return type and suggests a more specific type. This type is inferred from the actual code.

Loading history...
85
     */
86
    public function reset(array $credentials, Closure $callback)
87
    {
88
        $user = $this->validateReset($credentials);
89
90
        // If the responses from the validate method is not a user instance, we will
91
        // assume that it is a redirect and simply return it from this method and
92
        // the user is properly redirected having an error message on the post.
93
        if (! $user instanceof CanResetPasswordContract) {
94
            return $user;
95
        }
96
97
        $password = $credentials['password'];
98
99
        // Once the reset has been validated, we'll call the given callback with the
100
        // new password. This gives the user an opportunity to store the password
101
        // in their persistent storage.
102
        $callback($user, $password);
103
104
        return static::PASSWORD_RESET;
105
    }
106
107
    /**
108
     * Get the user for the given credentials.
109
     *
110
     * @param array $credentials
111
     *
112
     * @throws \UnexpectedValueException
113
     *
114
     * @return \Rinvex\Auth\Contracts\CanResetPasswordContract|null
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...
115
     */
116
    public function getUser(array $credentials): ?CanResetPasswordContract
117
    {
118
        $user = $this->users->retrieveByCredentials(array_only($credentials, ['email']));
119
120
        if ($user && ! $user instanceof CanResetPasswordContract) {
121
            throw new UnexpectedValueException('User must implement CanResetPassword interface.');
122
        }
123
124
        return $user;
125
    }
126
127
    /**
128
     * Create a new password reset token for the given user.
129
     *
130
     * @param \Rinvex\Auth\Contracts\CanResetPasswordContract $user
131
     * @param int                                             $expiration
132
     *
133
     * @return string
134
     */
135
    public function createToken(CanResetPasswordContract $user, $expiration): string
136
    {
137
        $payload = $this->buildPayload($user, $user->getEmailForPasswordReset(), $expiration);
138
139
        return hash_hmac('sha256', $payload, $this->getKey());
140
    }
141
142
    /**
143
     * Validate the given password reset token.
144
     *
145
     * @param \Rinvex\Auth\Contracts\CanResetPasswordContract $user
146
     * @param array                                           $credentials
147
     *
148
     * @return bool
149
     */
150
    public function validateToken(CanResetPasswordContract $user, array $credentials): bool
151
    {
152
        $payload = $this->buildPayload($user, $credentials['email'], $credentials['expiration']);
153
154
        return hash_equals($credentials['token'], hash_hmac('sha256', $payload, $this->getKey()));
155
    }
156
157
    /**
158
     * Validate the given expiration timestamp.
159
     *
160
     * @param int $expiration
161
     *
162
     * @return bool
163
     */
164
    public function validateTimestamp($expiration): bool
165
    {
166
        return now()->createFromTimestamp($expiration)->isFuture();
167
    }
168
169
    /**
170
     * Return the application key.
171
     *
172
     * @return string
173
     */
174
    public function getKey(): string
175
    {
176
        if (starts_with($this->key, 'base64:')) {
177
            return base64_decode(mb_substr($this->key, 7));
178
        }
179
180
        return $this->key;
181
    }
182
183
    /**
184
     * Returns the payload string containing.
185
     *
186
     * @param \Rinvex\Auth\Contracts\CanResetPasswordContract $user
187
     * @param string                                          $email
188
     * @param int                                             $expiration
189
     *
190
     * @return string
191
     */
192
    protected function buildPayload(CanResetPasswordContract $user, $email, $expiration): string
193
    {
194
        return implode(';', [
195
            $email,
196
            $expiration,
197
            $user->getKey(),
0 ignored issues
show
Bug introduced by
The method getKey() does not seem to exist on object<Rinvex\Auth\Contr...nResetPasswordContract>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
198
            $user->password,
0 ignored issues
show
Bug introduced by
Accessing password on the interface Rinvex\Auth\Contracts\CanResetPasswordContract 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...
199
        ]);
200
    }
201
202
    /**
203
     * Validate a password reset for the given credentials.
204
     *
205
     * @param array $credentials
206
     *
207
     * @return \Illuminate\Contracts\Auth\CanResetPassword|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...
208
     */
209
    protected function validateReset(array $credentials)
210
    {
211
        if (is_null($user = $this->getUser($credentials))) {
212
            return static::INVALID_USER;
213
        }
214
215
        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\Auth\Contr...nResetPasswordContract>.

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...
216
            return static::INVALID_TOKEN;
217
        }
218
219
        if (! $this->validateTimestamp($credentials['expiration'])) {
220
            return static::EXPIRED_TOKEN;
221
        }
222
223
        return $user;
224
    }
225
}
226