Completed
Push — master ( b1c4b6...a05019 )
by Şəhriyar
19s
created

app/Services/Registrar.php (4 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php namespace App\Services;
2
3
use App\Contracts\Registrar as RegistrarContract;
4
use App\Events\Users\LoggedIn;
5
use App\Events\Users\LoggedOut;
6
use App\Events\Users\Registered;
7
use App\Events\Users\RequestedResetPasswordLink;
8
use App\Events\Users\ResetPassword;
9
use App\Events\Users\Updated;
10
use App\Exceptions\Common\ValidationException;
11
use App\Exceptions\Users\LoginNotValidException;
12
use App\Exceptions\Users\PasswordNotValidException;
13
use App\Exceptions\Users\TokenNotValidException;
14
use App\Models\User;
15
use App\Models\UserActivation;
16
use App\Models\UserOAuth;
17
use App\Traits\Users\Activates;
18
use Illuminate\Database\Eloquent\ModelNotFoundException;
19
use Illuminate\Foundation\Auth\AuthenticatesAndRegistersUsers;
20
use Illuminate\Foundation\Auth\ThrottlesLogins;
21
use Illuminate\Foundation\Validation\ValidatesRequests;
22
use Laravel\Socialite\AbstractUser as SocialiteUser;
23
24
class Registrar implements RegistrarContract
25
{
26
    use AuthenticatesAndRegistersUsers, Activates, ThrottlesLogins, ValidatesRequests;
27
28
    /** @var \Illuminate\Http\Request */
29
    protected $request;
30
31 20
    public function __construct()
32
    {
33 20
        $this->request = app('router')->getCurrentRequest();
34 20
    }
35
36
    /**
37
     * Create a new user instance after a valid registration.
38
     *
39
     * @return \Illuminate\Contracts\Auth\Authenticatable
40
     * @throws \App\Exceptions\Common\ValidationException
41
     */
42 3
    public function register()
43
    {
44 3
        $validator = app('validator')->make($this->request->all(), [
45 3
            'name' => 'sometimes|required|max:255',
46 3
            'email' => 'required|email|max:255|unique:users',
47 3
            'password' => 'required|confirmed|min:' . config('auth.passwords.users.min_length'),
48 3
        ]);
49 3
        if ($validator->fails()) {
50 2
            throw new ValidationException($validator);
51
        }
52
53 1
        $user = new User();
54 1
        $this->request->has('name') && $user->name = $this->request->input('name');
55 1
        $user->email = $this->request->input('email');
56 1
        $user->password = app('hash')->make($this->request->input('password'));
57 1
        $user->save() && event(new Registered($user));
58
59 1
        return $user;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $user; (App\Models\User) is incompatible with the return type declared by the interface App\Contracts\Registrar::register of type Cartalyst\Sentinel\Users\UserInterface.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
60
    }
61
62
    /**
63
     * @param SocialiteUser $oauthUserData
64
     * @param string        $provider
65
     *
66
     * @return \Illuminate\Contracts\Auth\Authenticatable|bool
67
     */
68 1
    public function registerViaOAuth(SocialiteUser $oauthUserData, $provider)
69
    {
70
        /** @var \App\Models\User $ownerAccount */
71
        if (!($ownerAccount = User::withTrashed()->whereEmail($oauthUserData->email)->first())) {
72
            $ownerAccount = User::create([
73
                'name' => $oauthUserData->name,
74
                'email' => $oauthUserData->email,
75 1
                'password' => app('hash')->make(uniqid("", true))
76
            ]);
77
            event(new Registered($ownerAccount, $provider));
78
        }
79
80
        # If user account is soft-deleted, restore it.
81
        $ownerAccount->trashed() && $ownerAccount->restore();
82
83
        # Update missing user name.
84
        if (!$ownerAccount->name) {
85
            $ownerAccount->name = $oauthUserData->name;
86
            $ownerAccount->save();
87
        }
88
89
        ($doLinkOAuthAccount = $this->linkOAuthAccount($oauthUserData, $provider, $ownerAccount)) && app('auth.driver')->login($ownerAccount, true);
90
91
        event(new LoggedIn($ownerAccount, $provider));
92
93
        return $doLinkOAuthAccount;
94
    }
95
96
    /**
97
     * @param string $token
98
     *
99
     * @return bool
100
     * @throws \App\Exceptions\Common\ValidationException
101
     * @throws \App\Exceptions\Users\TokenNotValidException
102
     */
103 2
    public function activate($token = null)
104
    {
105 2
        $data = !is_null($token) ? ['token' => $token] : $this->request->all();
106 2
        $validator = app('validator')->make($data, [
107 2
            'token' => 'required|string',
108 2
        ]);
109 2
        if ($validator->fails()) {
110
            throw new ValidationException($validator);
111
        }
112
113 2
        $activation = UserActivation::whereCode($data['token'])->first();
0 ignored issues
show
The method first does only exist in Illuminate\Database\Query\Builder, but not in App\Models\UserActivation.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
114 2
        if (!$activation) {
115 1
            throw new TokenNotValidException;
116
        }
117
        /** @var \App\Models\User $user */
118 1
        $user = User::findOrFail($activation->user_id);
119
120 1
        return $this->complete($user, $data['token']);
121
    }
122
123
    /**
124
     * @param integer $id
125
     *
126
     * @return boolean
127
     * @throws \App\Exceptions\Common\ValidationException
128
     * @throws \Illuminate\Database\Eloquent\ModelNotFoundException
129
     */
130 2
    public function delete($id)
131
    {
132 2
        $validator = app('validator')->make($this->request->all(), [
133 2
            'password' => 'required|min:' . config('auth.passwords.users.min_length'),
134 2
        ]);
135 2
        if ($validator->fails()) {
136
            throw new ValidationException($validator);
137
        }
138
139 2
        $user = $this->get($id);
140 2
        if (!app('hash')->check($this->request->input("password"), $user->password)) {
141 1
            throw new PasswordNotValidException;
142
        }
143
144 1
        return (bool)User::destroy($id);
145
    }
146
147
    /**
148
     * @param integer $id
149
     *
150
     * @return \App\Models\User
151
     * @throws \Illuminate\Database\Eloquent\ModelNotFoundException
152
     */
153 7
    public function get($id)
154
    {
155 7
        if (!empty($user = User::findOrFail($id))) {
156 6
            return $user;
157
        }
158
159
        throw new ModelNotFoundException;
160
    }
161
162
    /**
163
     * @param integer $id
164
     *
165
     * @return boolean
166
     * @throws \App\Exceptions\Common\ValidationException
167
     */
168 2
    public function update($id)
169
    {
170 2
        $user = $this->get($id);
171
172 2
        $validator = app('validator')->make($this->request->all(), [
173 2
            'name' => 'sometimes|required|max:255',
174
            'email' => 'required|email|max:255|unique:users,email,' . $id
175 2
        ]);
176 2
        if ($validator->fails()) {
177 1
            throw new ValidationException($validator);
178
        }
179
180 1
        $userBefore = clone $user;
181
182 1
        $this->request->has('name') && $user->name = $this->request->input("name");
183 1
        $user->email = $this->request->input("email");
184
185 1
        return $user->save() && event(new Updated($userBefore, $user)); // Fire the event on success only!
186
    }
187
188
    /**
189
     * @return bool|\App\Models\User
190
     * @throws \App\Exceptions\Common\ValidationException
191
     * @throws \App\Exceptions\Users\LoginNotValidException
192
     */
193 2
    public function login()
194
    {
195 2
        $validator = app('validator')->make($this->request->all(), [
196 2
            'email' => 'required|email',
197 2
            'password' => 'required',
198 2
        ]);
199 2
        if ($validator->fails()) {
200 1
            throw new ValidationException($validator);
201
        }
202
203 2
        $credentials = $this->request->only('email', 'password');
204
205 2
        if (app('auth.driver')->attempt($credentials, $this->request->has('remember'))) {
206 1
            $user = app('auth.driver')->user();
207
208 1
            event(new LoggedIn($user));
209
210 1
            return $user;
211
        }
212
213 1
        throw new LoginNotValidException($this->getFailedLoginMessage());
214
    }
215
216
    /**
217
     * @param SocialiteUser $oauthUserData
218
     * @param string        $provider
219
     *
220
     * @return bool
221
     */
222
    public function loginViaOAuth(SocialiteUser $oauthUserData, $provider)
223
    {
224
        /** @var UserOAuth $owningOAuthAccount */
225
        if ($owningOAuthAccount = UserOAuth::whereRemoteProvider($provider)->whereRemoteId($oauthUserData->id)->first()) {
226
            $ownerAccount = $owningOAuthAccount->owner;
227
            app('auth.driver')->login($ownerAccount, true);
228
229
            event(new LoggedIn($ownerAccount, $provider));
230
231
            return true;
232
        }
233
234
        return !$this->registerViaOAuth($oauthUserData, $provider) ? false : true;
235
    }
236
237
    /**
238
     * @return void
239
     */
240 1
    public function logout()
241
    {
242 1
        $user = app('auth.driver')->user();
243 1
        if (!empty($user)) {
244 1
            app('auth.driver')->logout();
245 1
            app('events')->fire(new LoggedOut($user));
246 1
        }
247 1
    }
248
249
    /**
250
     * @return boolean
251
     * @throws \App\Exceptions\Common\ValidationException
252
     * @throws \Illuminate\Database\Eloquent\ModelNotFoundException
253
     */
254 2
    public function sendResetPasswordLinkViaEmail()
255
    {
256 2
        $validator = app('validator')->make($this->request->all(), [
257
            'email' => 'required|email|max:255'
258 2
        ]);
259 2
        if ($validator->fails()) {
260 1
            throw new ValidationException($validator);
261
        }
262
263 2
        $user = User::whereEmail($this->request->only('email'))->first();
264 2
        if (is_null($user)) {
265 1
            throw new ModelNotFoundException(trans('passwords.user'));
266
        }
267
268 1
        event(new RequestedResetPasswordLink($user));
269
270 1
        return true;
271
    }
272
273
    /**
274
     * @return boolean
275
     * @throws \App\Exceptions\Common\ValidationException
276
     * @throws \App\Exceptions\Users\TokenNotValidException
277
     * @throws \Illuminate\Database\Eloquent\ModelNotFoundException
278
     */
279 2
    public function resetPassword()
280
    {
281 2
        $validator = app('validator')->make($this->request->all(), [
282 2
            'token' => 'required|string',
283 2
            'email' => 'required|email|max:255',
284 2
            'password' => 'required|confirmed|min:' . app('config')->get('auth.passwords.users.min_length')
285 2
        ]);
286 2
        if ($validator->fails()) {
287 1
            throw new ValidationException($validator);
288
        }
289
290 2
        $credentials = $this->request->only('email', 'password', 'password_confirmation', 'token');
291
292 2
        $passwordBroker = app('auth.password.broker');
293 2
        $response = $passwordBroker->reset(
294 2
            $credentials, function (User $user, $password) {
295 1
            $user->password = app('hash')->make($password);
296 1
            $user->save();
297 1
            app('auth.driver')->login($user);
298 2
        });
299
300
        switch ($response) {
301 2
            case $passwordBroker::INVALID_USER:
302 1
                throw new ModelNotFoundException(trans($response));
303
                break;
0 ignored issues
show
break; does not seem to be reachable.

This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.

Unreachable code is most often the result of return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

In the above example, the last return false will never be executed, because a return statement has already been met in every possible execution path.

Loading history...
304 2
            case $passwordBroker::INVALID_TOKEN:
305 1
                throw new TokenNotValidException(trans($response));
306
                break;
0 ignored issues
show
break; does not seem to be reachable.

This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.

Unreachable code is most often the result of return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

In the above example, the last return false will never be executed, because a return statement has already been met in every possible execution path.

Loading history...
307
        }
308
309 1
        event(new ResetPassword(app('auth.driver')->user()));
310
311 1
        return true;
312
    }
313
314
    /**
315
     * @param SocialiteUser $oauthUserData
316
     * @param string        $provider
317
     * @param User          $ownerAccount
318
     *
319
     * @return \App\Models\User|bool
320
     */
321
    private function linkOAuthAccount(SocialiteUser $oauthUserData, $provider, $ownerAccount)
322
    {
323
        /** @var UserOAuth[] $linkedAccounts */
324
        $linkedAccounts = $ownerAccount->linkedAccounts()->ofProvider($provider)->get();
325
326
        foreach ($linkedAccounts as $linkedAccount) {
327
            if ($linkedAccount->remote_id === $oauthUserData->id || $linkedAccount->email === $oauthUserData->email) {
328
                $linkedAccount->remote_id = $oauthUserData->id;
329
                $linkedAccount->nickname = $oauthUserData->nickname;
330
                $linkedAccount->name = $oauthUserData->name;
331
                $linkedAccount->email = $oauthUserData->email;
332
                $linkedAccount->avatar = $oauthUserData->avatar;
333
334
                return $linkedAccount->save() ? $ownerAccount : false;
335
            }
336
        }
337
338
        $linkedAccount = new UserOAuth();
339
        $linkedAccount->remote_provider = $provider;
340
        $linkedAccount->remote_id = $oauthUserData->id;
341
        $linkedAccount->nickname = $oauthUserData->nickname;
342
        $linkedAccount->name = $oauthUserData->name;
343
        $linkedAccount->email = $oauthUserData->email;
344
        $linkedAccount->avatar = $oauthUserData->avatar;
345
346
        return $ownerAccount->linkedAccounts()->save($linkedAccount) ? $ownerAccount : false;
347
    }
348
}
349