1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
declare(strict_types=1); |
4
|
|
|
|
5
|
|
|
namespace Gewaer\Api\Controllers; |
6
|
|
|
|
7
|
|
|
use Gewaer\Models\UsersInvite; |
8
|
|
|
use Gewaer\Models\Users; |
9
|
|
|
use Gewaer\Models\UsersAssociatedCompany; |
10
|
|
|
use Gewaer\Models\Roles; |
11
|
|
|
use Phalcon\Security\Random; |
12
|
|
|
use Phalcon\Validation; |
13
|
|
|
use Phalcon\Validation\Validator\PresenceOf; |
14
|
|
|
use Phalcon\Validation\Validator\StringLength; |
15
|
|
|
use Gewaer\Exception\UnprocessableEntityHttpException; |
16
|
|
|
use Gewaer\Exception\NotFoundHttpException; |
17
|
|
|
use Gewaer\Exception\ServerErrorHttpException; |
18
|
|
|
use Phalcon\Http\Response; |
19
|
|
|
use Exception; |
20
|
|
|
use Baka\Auth\Models\Sessions; |
21
|
|
|
use Gewaer\Exception\ModelException; |
22
|
|
|
|
23
|
|
|
/** |
24
|
|
|
* Class LanguagesController |
25
|
|
|
* @property Users $userData |
26
|
|
|
* @property Request $request |
27
|
|
|
* @property Config $config |
28
|
|
|
* @property Apps $app |
29
|
|
|
* @property Mail $mail |
30
|
|
|
* @property Auth $auth |
31
|
|
|
* @package Gewaer\Api\Controllers |
32
|
|
|
* |
33
|
|
|
*/ |
34
|
|
|
class UsersInviteController extends BaseController |
35
|
|
|
{ |
36
|
|
|
/* |
37
|
|
|
* fields we accept to create |
38
|
|
|
* |
39
|
|
|
* @var array |
40
|
|
|
*/ |
41
|
|
|
protected $createFields = ['invite_hash', 'companies_id', 'role_id', 'app_id', 'email']; |
42
|
|
|
|
43
|
|
|
/* |
44
|
|
|
* fields we accept to create |
45
|
|
|
* |
46
|
|
|
* @var array |
47
|
|
|
*/ |
48
|
|
|
protected $updateFields = ['invite_hash', 'companies_id', 'role_id', 'app_id', 'email']; |
49
|
|
|
|
50
|
|
|
/** |
51
|
|
|
* set objects |
52
|
|
|
* |
53
|
|
|
* @return void |
54
|
|
|
*/ |
55
|
4 |
|
public function onConstruct() |
56
|
|
|
{ |
57
|
4 |
|
$this->model = new UsersInvite(); |
58
|
4 |
|
$this->additionalSearchFields = [ |
59
|
4 |
|
['is_deleted', ':', '0'], |
60
|
4 |
|
['companies_id', ':', $this->userData->currentCompanyId()], |
61
|
|
|
]; |
62
|
4 |
|
} |
63
|
|
|
|
64
|
|
|
/** |
65
|
|
|
* Get users invite by hash |
66
|
|
|
* @param string $hash |
67
|
|
|
* @return Response |
68
|
|
|
*/ |
69
|
1 |
|
public function getByHash(string $hash):Response |
70
|
|
|
{ |
71
|
1 |
|
$userInvite = $this->model::findFirst([ |
72
|
1 |
|
'conditions' => 'invite_hash = ?0 and is_deleted = 0', |
73
|
1 |
|
'bind' => [$hash] |
74
|
|
|
]); |
75
|
|
|
|
76
|
1 |
|
if (!is_object($userInvite)) { |
77
|
|
|
throw new NotFoundHttpException('Users Invite not found'); |
78
|
|
|
} |
79
|
|
|
|
80
|
1 |
|
return $this->response($userInvite); |
81
|
|
|
} |
82
|
|
|
|
83
|
|
|
/** |
84
|
|
|
* Sets up invitation information for a would be user |
85
|
|
|
* @return Response |
86
|
|
|
*/ |
87
|
4 |
|
public function insertInvite(): Response |
88
|
|
|
{ |
89
|
4 |
|
$request = $this->request->getPost(); |
90
|
4 |
|
$random = new Random(); |
91
|
|
|
|
92
|
4 |
|
$validation = new Validation(); |
93
|
4 |
|
$validation->add('email', new PresenceOf(['message' => _('The email is required.')])); |
94
|
4 |
|
$validation->add('role_id', new PresenceOf(['message' => _('The role is required.')])); |
95
|
|
|
|
96
|
|
|
//validate this form for password |
97
|
4 |
|
$messages = $validation->validate($this->request->getPost()); |
98
|
4 |
|
if (count($messages)) { |
99
|
|
|
foreach ($messages as $message) { |
100
|
|
|
throw new ServerErrorHttpException((string)$message); |
101
|
|
|
} |
102
|
|
|
} |
103
|
|
|
|
104
|
|
|
//Check if user was already was invited to current company and return message |
105
|
4 |
|
$invitedUser = $this->model::findFirst([ |
106
|
4 |
|
'conditions' => 'email = ?0 and companies_id = ?1 and role_id = ?2', |
107
|
4 |
|
'bind' => [$request['email'], $this->userData->default_company, $request['role_id']] |
108
|
|
|
]); |
109
|
|
|
|
110
|
4 |
|
if (is_object($invitedUser)) { |
111
|
|
|
throw new ModelException('User already invited to this company and added with this role'); |
112
|
|
|
} |
113
|
|
|
|
114
|
|
|
//Save data to users_invite table and generate a hash for the invite |
115
|
4 |
|
$userInvite = $this->model; |
116
|
4 |
|
$userInvite->companies_id = $this->userData->default_company; |
117
|
4 |
|
$userInvite->app_id = $this->app->getId(); |
118
|
4 |
|
$userInvite->role_id = Roles::getById((int)$request['role_id'])->id; |
119
|
4 |
|
$userInvite->email = $request['email']; |
120
|
4 |
|
$userInvite->invite_hash = $random->base58(); |
121
|
4 |
|
$userInvite->created_at = date('Y-m-d H:m:s'); |
122
|
|
|
|
123
|
4 |
|
if (!$userInvite->save()) { |
124
|
|
|
throw new UnprocessableEntityHttpException((string) current($userInvite->getMessages())); |
125
|
|
|
} |
126
|
|
|
|
127
|
4 |
|
$this->sendInviteEmail($request['email'], $userInvite->invite_hash); |
128
|
4 |
|
return $this->response($userInvite); |
129
|
|
|
} |
130
|
|
|
|
131
|
|
|
/** |
132
|
|
|
* Send users invite email |
133
|
|
|
* @param string $email |
134
|
|
|
* @return void |
135
|
|
|
*/ |
136
|
4 |
|
public function sendInviteEmail(string $email, string $hash): void |
137
|
|
|
{ |
138
|
4 |
|
$userExists = Users::findFirst([ |
139
|
4 |
|
'conditions' => 'email = ?0 and is_deleted = 0', |
140
|
4 |
|
'bind' => [$email] |
141
|
|
|
]); |
142
|
|
|
|
143
|
4 |
|
$invitationUrl = $this->config->app->frontEndUrl . '/users/invites/' . $hash; |
144
|
|
|
|
145
|
4 |
|
if (is_object($userExists)) { |
146
|
|
|
$invitationUrl = $this->config->app->frontEndUrl . '/users/link/' . $hash; |
147
|
|
|
} |
148
|
|
|
|
149
|
4 |
|
if (!defined('API_TESTS')) { |
150
|
|
|
$subject = _('You have been invited!'); |
151
|
|
|
$this->mail |
152
|
|
|
->to($email) |
153
|
|
|
->subject($subject) |
154
|
|
|
->content($invitationUrl) |
155
|
|
|
->sendNow(); |
156
|
|
|
} |
157
|
4 |
|
} |
158
|
|
|
|
159
|
|
|
/** |
160
|
|
|
* Add invited user to our system |
161
|
|
|
* @return Response |
162
|
|
|
*/ |
163
|
2 |
|
public function processUserInvite(string $hash): Response |
164
|
|
|
{ |
165
|
2 |
|
$request = $this->request->getPost(); |
166
|
2 |
|
$password = ltrim(trim($request['password'])); |
167
|
|
|
|
168
|
2 |
|
if (empty($request)) { |
169
|
|
|
$request = $this->request->getJsonRawBody(true); |
170
|
|
|
} |
171
|
|
|
|
172
|
|
|
//Ok let validate user password |
173
|
2 |
|
$validation = new Validation(); |
174
|
2 |
|
$validation->add('password', new PresenceOf(['message' => _('The password is required.')])); |
175
|
|
|
|
176
|
2 |
|
$validation->add( |
177
|
2 |
|
'password', |
178
|
2 |
|
new StringLength([ |
179
|
2 |
|
'min' => 8, |
180
|
2 |
|
'messageMinimum' => _('Password is too short. Minimum 8 characters.'), |
181
|
|
|
]) |
182
|
|
|
); |
183
|
|
|
|
184
|
|
|
//validate this form for password |
185
|
2 |
|
$messages = $validation->validate($request); |
186
|
2 |
|
if (count($messages)) { |
187
|
|
|
foreach ($messages as $message) { |
188
|
|
|
throw new ServerErrorHttpException((string)$message); |
189
|
|
|
} |
190
|
|
|
} |
191
|
|
|
|
192
|
|
|
//Lets find users_invite by hash on our database |
193
|
2 |
|
$usersInvite = $this->model::findFirst([ |
194
|
2 |
|
'conditions' => 'invite_hash = ?0 and is_deleted = 0', |
195
|
2 |
|
'bind' => [$hash] |
196
|
|
|
]); |
197
|
|
|
|
198
|
2 |
|
if (!is_object($usersInvite)) { |
199
|
|
|
throw new NotFoundHttpException('Users Invite not found'); |
200
|
|
|
} |
201
|
|
|
|
202
|
|
|
//Check if user already exists |
203
|
2 |
|
$userExists = Users::findFirst([ |
204
|
2 |
|
'conditions' => 'email = ?0 and is_deleted = 0', |
205
|
2 |
|
'bind' => [$usersInvite->email] |
206
|
|
|
]); |
207
|
|
|
|
208
|
2 |
|
if (is_object($userExists)) { |
209
|
1 |
|
$newUser = new UsersAssociatedCompany; |
210
|
1 |
|
$newUser->users_id = (int)$userExists->id; |
211
|
1 |
|
$newUser->companies_id = (int)$userExists->default_company; |
212
|
1 |
|
$newUser->identify_id = $userExists->roles_id; |
|
|
|
|
213
|
1 |
|
$newUser->user_active = 1; |
214
|
1 |
|
$newUser->user_role = Roles::getById((int)$userExists->roles_id)->name; |
215
|
|
|
|
216
|
1 |
|
if (!$newUser->save()) { |
217
|
1 |
|
throw new UnprocessableEntityHttpException((string) current($newUser->getMessages())); |
218
|
|
|
} |
219
|
|
|
} else { |
220
|
2 |
|
$newUser = new Users(); |
221
|
2 |
|
$newUser->firstname = $request['firstname']; |
222
|
2 |
|
$newUser->lastname = $request['lastname']; |
223
|
2 |
|
$newUser->displayname = $request['displayname']; |
224
|
2 |
|
$newUser->password = $password; |
225
|
2 |
|
$newUser->email = $usersInvite->email; |
226
|
2 |
|
$newUser->user_active = 1; |
227
|
2 |
|
$newUser->roles_id = $usersInvite->role_id; |
228
|
2 |
|
$newUser->created_at = date('Y-m-d H:m:s'); |
229
|
2 |
|
$newUser->default_company = $usersInvite->companies_id; |
230
|
2 |
|
$newUser->default_company_branch = $usersInvite->company->branch->getId(); |
231
|
|
|
|
232
|
|
|
try { |
233
|
2 |
|
$this->db->begin(); |
234
|
|
|
|
235
|
|
|
//signup |
236
|
2 |
|
$newUser->signup(); |
237
|
|
|
|
238
|
2 |
|
$this->db->commit(); |
239
|
|
|
} catch (Exception $e) { |
240
|
|
|
$this->db->rollback(); |
241
|
|
|
|
242
|
|
|
throw new UnprocessableEntityHttpException($e->getMessage()); |
243
|
|
|
} |
244
|
|
|
} |
245
|
|
|
|
246
|
|
|
//Lets login the new user |
247
|
2 |
|
$authInfo = $this->loginInvitedUser($usersInvite->email, $password); |
248
|
|
|
|
249
|
2 |
|
if (!defined('API_TESTS')) { |
250
|
|
|
$usersInvite->is_deleted = 1; |
251
|
|
|
$usersInvite->update(); |
252
|
|
|
|
253
|
|
|
return $this->response($authInfo); |
254
|
|
|
} |
255
|
|
|
|
256
|
2 |
|
return $this->response($newUser); |
257
|
|
|
} |
258
|
|
|
|
259
|
|
|
/** |
260
|
|
|
* Login invited user |
261
|
|
|
* @param string |
262
|
|
|
* @return array |
263
|
|
|
*/ |
264
|
2 |
|
public function loginInvitedUser(string $email, string $password): array |
265
|
|
|
{ |
266
|
2 |
|
$userIp = !defined('API_TESTS') ? $this->request->getClientAddress() : '127.0.0.1'; |
267
|
|
|
|
268
|
2 |
|
$random = new \Phalcon\Security\Random(); |
269
|
|
|
|
270
|
2 |
|
$userData = Users::login($email, $password, 1, 0, $userIp); |
271
|
|
|
|
272
|
2 |
|
$sessionId = $random->uuid(); |
273
|
|
|
|
274
|
|
|
//save in user logs |
275
|
|
|
$payload = [ |
276
|
2 |
|
'sessionId' => $sessionId, |
277
|
2 |
|
'email' => $userData->getEmail(), |
278
|
2 |
|
'iat' => time(), |
279
|
|
|
]; |
280
|
|
|
|
281
|
2 |
|
$token = $this->auth->make($payload); |
282
|
|
|
|
283
|
|
|
//start session |
284
|
2 |
|
$session = new Sessions(); |
285
|
2 |
|
$session->start($userData, $sessionId, $token, $userIp, 1); |
286
|
|
|
|
287
|
|
|
return [ |
288
|
2 |
|
'token' => $token, |
289
|
2 |
|
'time' => date('Y-m-d H:i:s'), |
290
|
2 |
|
'expires' => date('Y-m-d H:i:s', time() + $this->config->jwt->payload->exp), |
291
|
2 |
|
'id' => $userData->getId(), |
292
|
|
|
]; |
293
|
|
|
} |
294
|
|
|
} |
295
|
|
|
|
Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.
For example, imagine you have a variable
$accountId
that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to theid
property of an instance of theAccount
class. This class holds a proper account, so the id value must no longer be false.Either this assignment is in error or a type check should be added for that assignment.