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; |
|
|
|
|
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(); |
|
|
|
|
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; |
|
|
|
|
304
|
2 |
|
case $passwordBroker::INVALID_TOKEN: |
305
|
1 |
|
throw new TokenNotValidException(trans($response)); |
306
|
|
|
break; |
|
|
|
|
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
|
|
|
|
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:
Our function
my_function
expects aPost
object, and outputs the author of the post. The base classPost
returns a simple string and outputting a simple string will work just fine. However, the child classBlogPost
which is a sub-type ofPost
instead decided to return anobject
, and is therefore violating the SOLID principles. If aBlogPost
were passed tomy_function
, PHP would not complain, but ultimately fail when executing thestrtoupper
call in its body.