1 | <?php |
||
2 | |||
3 | /* |
||
4 | * This file is part of the Ocrend Framewok 2 package. |
||
5 | * |
||
6 | * (c) Ocrend Software <[email protected]> |
||
7 | * |
||
8 | * For the full copyright and license information, please view the LICENSE |
||
9 | * file that was distributed with this source code. |
||
10 | */ |
||
11 | |||
12 | namespace app\models; |
||
13 | |||
14 | use app\models as Model; |
||
15 | use Ocrend\Kernel\Models\Models; |
||
16 | use Ocrend\Kernel\Models\IModels; |
||
17 | use Ocrend\Kernel\Models\ModelsException; |
||
18 | use Ocrend\Kernel\Models\Traits\DBModel; |
||
19 | use Ocrend\Kernel\Router\IRouter; |
||
20 | use Ocrend\Kernel\Helpers\Strings; |
||
21 | use Ocrend\Kernel\Helpers\Emails; |
||
22 | |||
23 | /** |
||
24 | * Controla todos los aspectos de un usuario dentro del sistema. |
||
25 | * |
||
26 | * @author Brayan Narváez <[email protected]> |
||
27 | */ |
||
28 | |||
29 | class Users extends Models implements IModels { |
||
30 | /** |
||
31 | * Característica para establecer conexión con base de datos. |
||
32 | */ |
||
33 | use DBModel; |
||
34 | |||
35 | /** |
||
36 | * Máximos intentos de inincio de sesión de un usuario |
||
37 | * |
||
38 | * @var int |
||
39 | */ |
||
40 | const MAX_ATTEMPTS = 5; |
||
41 | |||
42 | /** |
||
43 | * Tiempo entre máximos intentos en segundos |
||
44 | * |
||
45 | * @var int |
||
46 | */ |
||
47 | const MAX_ATTEMPTS_TIME = 120; # (dos minutos) |
||
48 | |||
49 | /** |
||
50 | * Log de intentos recientes con la forma 'email' => (int) intentos |
||
51 | * |
||
52 | * @var array |
||
53 | */ |
||
54 | private $recentAttempts = array(); |
||
55 | |||
56 | /** |
||
57 | * Hace un set() a la sesión login_user_recentAttempts con el valor actualizado. |
||
58 | * |
||
59 | * @return void |
||
60 | */ |
||
61 | private function updateSessionAttempts() { |
||
62 | global $session; |
||
63 | |||
64 | $session->set('login_user_recentAttempts', $this->recentAttempts); |
||
65 | } |
||
66 | |||
67 | /** |
||
68 | * Genera la sesión con el id del usuario que ha iniciado |
||
69 | * |
||
70 | * @param string $pass : Contraseña sin encriptar |
||
71 | * @param string $pass_repeat : Contraseña repetida sin encriptar |
||
72 | * |
||
73 | * @throws ModelsException cuando las contraseñas no coinciden |
||
74 | */ |
||
75 | private function checkPassMatch(string $pass, string $pass_repeat) { |
||
76 | if ($pass != $pass_repeat) { |
||
77 | throw new ModelsException('Las contraseñas no coinciden.'); |
||
78 | } |
||
79 | } |
||
80 | |||
81 | /** |
||
82 | * Verifica el email introducido, tanto el formato como su existencia en el sistema |
||
83 | * |
||
84 | * @param string $email: Email del usuario |
||
85 | * |
||
86 | * @throws ModelsException en caso de que no tenga formato válido o ya exista |
||
87 | */ |
||
88 | private function checkEmail(string $email) { |
||
89 | # Formato de email |
||
90 | if (!Strings::is_email($email)) { |
||
91 | throw new ModelsException('El email no tiene un formato válido.'); |
||
92 | } |
||
93 | # Existencia de email |
||
94 | $email = $this->db->scape($email); |
||
0 ignored issues
–
show
|
|||
95 | $query = $this->db->select('id_user', 'users', "email='$email'", 'LIMIT 1'); |
||
96 | if (false !== $query) { |
||
97 | throw new ModelsException('El email introducido ya existe.'); |
||
98 | } |
||
99 | } |
||
100 | |||
101 | /** |
||
102 | * Restaura los intentos de un usuario al iniciar sesión |
||
103 | * |
||
104 | * @param string $email: Email del usuario a restaurar |
||
105 | * |
||
106 | * @throws ModelsException cuando hay un error de lógica utilizando este método |
||
107 | * @return void |
||
108 | */ |
||
109 | private function restoreAttempts(string $email) { |
||
110 | if (array_key_exists($email, $this->recentAttempts)) { |
||
111 | $this->recentAttempts[$email]['attempts'] = 0; |
||
112 | $this->recentAttempts[$email]['time'] = null; |
||
113 | $this->updateSessionAttempts(); |
||
114 | } else { |
||
115 | throw new ModelsException('Error lógico'); |
||
116 | } |
||
117 | |||
118 | } |
||
119 | |||
120 | /** |
||
121 | * Genera la sesión con el id del usuario que ha iniciado |
||
122 | * |
||
123 | * @param array $user_data: Arreglo con información de la base de datos, del usuario |
||
124 | * |
||
125 | * @return void |
||
126 | */ |
||
127 | private function generateSession(array $user_data) { |
||
128 | global $session, $config; |
||
129 | |||
130 | $session->set($config['sessions']['unique'] . '_user_id',(int) $user_data['id_user']); |
||
131 | } |
||
132 | |||
133 | /** |
||
134 | * Verifica en la base de datos, el email y contraseña ingresados por el usuario |
||
135 | * |
||
136 | * @param string $email: Email del usuario que intenta el login |
||
137 | * @param string $pass: Contraseña sin encriptar del usuario que intenta el login |
||
138 | * |
||
139 | * @return bool true: Cuando el inicio de sesión es correcto |
||
140 | * false: Cuando el inicio de sesión no es correcto |
||
141 | */ |
||
142 | private function authentication(string $email,string $pass) : bool { |
||
143 | $email = $this->db->scape($email); |
||
144 | $query = $this->db->select('id_user,pass','users',"email='$email'",'LIMIT 1'); |
||
145 | |||
146 | # Incio de sesión con éxito |
||
147 | if(false !== $query && Strings::chash($query[0]['pass'],$pass)) { |
||
148 | |||
149 | # Restaurar intentos |
||
150 | $this->restoreAttempts($email); |
||
151 | |||
152 | # Generar la sesión |
||
153 | $this->generateSession($query[0]); |
||
154 | return true; |
||
155 | } |
||
156 | |||
157 | return false; |
||
158 | } |
||
159 | |||
160 | /** |
||
161 | * Establece los intentos recientes desde la variable de sesión acumulativa |
||
162 | * |
||
163 | * @return void |
||
164 | */ |
||
165 | private function setDefaultAttempts() { |
||
166 | global $session; |
||
167 | |||
168 | if (null != $session->get('login_user_recentAttempts')) { |
||
169 | $this->recentAttempts = $session->get('login_user_recentAttempts'); |
||
170 | } |
||
171 | } |
||
172 | |||
173 | /** |
||
174 | * Establece el intento del usuario actual o incrementa su cantidad si ya existe |
||
175 | * |
||
176 | * @param string $email: Email del usuario |
||
177 | * |
||
178 | * @return void |
||
179 | */ |
||
180 | private function setNewAttempt(string $email) { |
||
181 | if (!array_key_exists($email, $this->recentAttempts)) { |
||
182 | $this->recentAttempts[$email] = array( |
||
183 | 'attempts' => 0, # Intentos |
||
184 | 'time' => null # Tiempo |
||
185 | ); |
||
186 | } |
||
187 | |||
188 | $this->recentAttempts[$email]['attempts']++; |
||
189 | $this->updateSessionAttempts(); |
||
190 | } |
||
191 | |||
192 | /** |
||
193 | * Controla la cantidad de intentos permitidos máximos por usuario, si llega al límite, |
||
194 | * el usuario podrá seguir intentando en self::MAX_ATTEMPTS_TIME segundos. |
||
195 | * |
||
196 | * @param string $email: Email del usuario |
||
197 | * |
||
198 | * @throws ModelsException cuando ya ha excedido self::MAX_ATTEMPTS |
||
199 | * @return void |
||
200 | */ |
||
201 | private function maximumAttempts(string $email) { |
||
202 | if ($this->recentAttempts[$email]['attempts'] >= self::MAX_ATTEMPTS) { |
||
203 | |||
204 | # Colocar timestamp para recuperar más adelante la posibilidad de acceso |
||
205 | if (null == $this->recentAttempts[$email]['time']) { |
||
206 | $this->recentAttempts[$email]['time'] = time() + self::MAX_ATTEMPTS_TIME; |
||
207 | } |
||
208 | |||
209 | if (time() < $this->recentAttempts[$email]['time']) { |
||
210 | # Setear sesión |
||
211 | $this->updateSessionAttempts(); |
||
212 | # Lanzar excepción |
||
213 | throw new ModelsException('Ya ha superado el límite de intentos para iniciar sesión.'); |
||
214 | } else { |
||
215 | $this->restoreAttempts($email); |
||
216 | } |
||
217 | } |
||
218 | } |
||
219 | |||
220 | /** |
||
221 | * Realiza la acción de login dentro del sistema |
||
222 | * |
||
223 | * @return array : Con información de éxito/falla al inicio de sesión. |
||
224 | */ |
||
225 | public function login() : array { |
||
226 | try { |
||
227 | global $http; |
||
228 | |||
229 | # Definir de nuevo el control de intentos |
||
230 | $this->setDefaultAttempts(); |
||
231 | |||
232 | # Obtener los datos $_POST |
||
233 | $email = strtolower($http->request->get('email')); |
||
234 | $pass = $http->request->get('pass'); |
||
235 | |||
236 | # Verificar que no están vacíos |
||
237 | if ($this->functions->e($email, $pass)) { |
||
238 | throw new ModelsException('Credenciales incompletas.'); |
||
239 | } |
||
240 | |||
241 | # Añadir intentos |
||
242 | $this->setNewAttempt($email); |
||
243 | |||
244 | # Verificar intentos |
||
245 | $this->maximumAttempts($email); |
||
246 | |||
247 | # Autentificar |
||
248 | if ($this->authentication($email, $pass)) { |
||
249 | return array('success' => 1, 'message' => 'Conectado con éxito.'); |
||
250 | } |
||
251 | |||
252 | throw new ModelsException('Credenciales incorrectas.'); |
||
253 | |||
254 | } catch (ModelsException $e) { |
||
255 | return array('success' => 0, 'message' => $e->getMessage()); |
||
256 | } |
||
257 | } |
||
258 | |||
259 | /** |
||
260 | * Realiza la acción de registro dentro del sistema |
||
261 | * |
||
262 | * @return array : Con información de éxito/falla al registrar el usuario nuevo. |
||
263 | */ |
||
264 | public function register() : array { |
||
265 | try { |
||
266 | global $http; |
||
267 | |||
268 | # Obtener los datos $_POST |
||
269 | $name = $http->request->get('name'); |
||
270 | $email = $http->request->get('email'); |
||
271 | $pass = $http->request->get('pass'); |
||
272 | $pass_repeat = $http->request->get('pass_repeat'); |
||
273 | |||
274 | # Verificar que no están vacíos |
||
275 | if ($this->functions->e($name, $email, $pass, $pass_repeat)) { |
||
276 | throw new ModelsException('Todos los datos son necesarios'); |
||
277 | } |
||
278 | |||
279 | # Verificar email |
||
280 | $this->checkEmail($email); |
||
281 | |||
282 | # Veriricar contraseñas |
||
283 | $this->checkPassMatch($pass, $pass_repeat); |
||
284 | |||
285 | # Registrar al usuario |
||
286 | $this->db->insert('users', array( |
||
287 | 'name' => $name, |
||
288 | 'email' => $email, |
||
289 | 'pass' => Strings::hash($pass) |
||
290 | )); |
||
291 | |||
292 | # Iniciar sesión |
||
293 | $this->generateSession(array( |
||
294 | 'id_user' => $this->db->lastInsertId() |
||
295 | )); |
||
296 | |||
297 | return array('success' => 1, 'message' => 'Registrado con éxito.'); |
||
298 | } catch (ModelsException $e) { |
||
299 | return array('success' => 0, 'message' => $e->getMessage()); |
||
300 | } |
||
301 | } |
||
302 | |||
303 | /** |
||
304 | * Envía un correo electrónico al usuario que quiere recuperar la contraseña, con un token y una nueva contraseña. |
||
305 | * Si el usuario no visita el enlace, el sistema no cambiará la contraseña. |
||
306 | * |
||
307 | * @return array<string,integer|string> |
||
308 | */ |
||
309 | public function lostpass() { |
||
310 | try { |
||
311 | global $http, $config; |
||
312 | |||
313 | # Obtener datos $_POST |
||
314 | $email = $http->request->get('email'); |
||
315 | |||
316 | # Campo lleno |
||
317 | if ($this->functions->emp($email)) { |
||
318 | throw new ModelsException('El campo email debe estar lleno.'); |
||
319 | } |
||
320 | |||
321 | # Filtro |
||
322 | $email = $this->db->scape($email); |
||
323 | |||
324 | # Obtener información del usuario |
||
325 | $user_data = $this->db->select('id_user,name', 'users', "email='$email'", 'LIMIT 1'); |
||
326 | |||
327 | # Verificar correo en base de datos |
||
328 | if (false === $user_data) { |
||
329 | throw new ModelsException('El email no está registrado en el sistema.'); |
||
330 | } |
||
331 | |||
332 | # Generar token y contraseña |
||
333 | $token = md5(time()); |
||
334 | $pass = uniqid(); |
||
335 | |||
336 | # Construir mensaje y enviar mensaje |
||
337 | $HTML = 'Hola <b>'. $user_data[0]['name'] .'</b>, ha solicitado recuperar su contraseña perdida, si no ha realizado esta acción no necesita hacer nada. |
||
338 | <br /> |
||
339 | <br /> |
||
340 | Para cambiar su contraseña por <b>'. $pass .'</b> haga <a href="'. $config['site']['url'] . 'lostpass/cambiar/&token='.$token.'&user='.$user_data[0]['id_user'].'" target="_blank">clic aquí</a>.'; |
||
341 | |||
342 | # Enviar el correo electrónico |
||
343 | $dest = array(); |
||
344 | $dest[$email] = $user_data[0]['name']; |
||
345 | $email = Emails::send_mail($dest,Emails::plantilla($HTML),'Recuperar contraseña perdida'); |
||
346 | |||
347 | # Verificar si hubo algún problema con el envío del correo |
||
348 | if(false === $email) { |
||
349 | throw new ModelsException('No se ha podido enviar el correo electrónico.'); |
||
350 | } |
||
351 | |||
352 | # Actualizar datos |
||
353 | $id_user = $user_data[0]['id_user']; |
||
354 | $this->db->update('users',array( |
||
355 | 'tmp_pass' => Strings::hash($pass), |
||
356 | 'token' => $token |
||
357 | ),"id_user='$id_user'",'LIMIT 1'); |
||
358 | |||
359 | return array('success' => 1, 'message' => 'Se ha enviado un enlace a su correo electrónico.'); |
||
360 | } catch(ModelsException $e) { |
||
361 | return array('success' => 0, 'message' => $e->getMessage()); |
||
362 | } |
||
363 | } |
||
364 | |||
365 | /** |
||
366 | * Cambia la contraseña de un usuario en el sistema, luego de que éste haya solicitado cambiarla. |
||
367 | * Luego retorna al sitio de inicio con la variable GET success=(bool) |
||
368 | * |
||
369 | * La URL debe tener la forma URL/lostpass/cambiar/&token=TOKEN&user=ID |
||
370 | * |
||
371 | * @return void |
||
372 | */ |
||
373 | public function changeTemporalPass() { |
||
374 | global $config, $http; |
||
375 | |||
376 | # Obtener los datos $_GET |
||
377 | $id_user = $http->query->get('user'); |
||
378 | $token = $http->query->get('token'); |
||
379 | |||
380 | if (!$this->functions->emp($token) && is_numeric($id_user) && $id_user >= 1) { |
||
381 | # Filtros a los datos |
||
382 | $id_user = $this->db->scape($id_user); |
||
383 | $token = $this->db->scape($token); |
||
384 | # Ejecutar el cambio |
||
385 | $this->db->query("UPDATE users SET pass=tmp_pass, tmp_pass='', token='' |
||
386 | WHERE id_user='$id_user' AND token='$token' LIMIT 1;"); |
||
387 | # Éxito |
||
388 | $success = true; |
||
389 | } |
||
390 | |||
391 | # Devolover al sitio de inicio |
||
392 | $this->functions->redir($config['site']['url'] . '?sucess=' . (int) isset($success)); |
||
393 | } |
||
394 | |||
395 | /** |
||
396 | * Desconecta a un usuario si éste está conectado, y lo devuelve al inicio |
||
397 | * |
||
398 | * @return void |
||
399 | */ |
||
400 | public function logout() { |
||
401 | global $session, $config; |
||
402 | |||
403 | View Code Duplication | if(null != $session->get($config['sessions']['unique'] . '_user_id')) { |
|
0 ignored issues
–
show
This code seems to be duplicated across your project.
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation. You can also find more detailed suggestions in the “Code” section of your repository.
Loading history...
|
|||
404 | $session->remove($config['sessions']['unique'] . '_user_id'); |
||
405 | } |
||
406 | |||
407 | $this->functions->redir(); |
||
408 | } |
||
409 | |||
410 | /** |
||
411 | * Obtiene datos de un usuario según su id en la base de datos |
||
412 | * |
||
413 | * @param int $id: Id del usuario a obtener |
||
414 | * @param string $select : Por defecto es *, se usa para obtener sólo los parámetros necesarios |
||
415 | * |
||
416 | * @return false|array con información del usuario |
||
417 | */ |
||
418 | public function getUserById(int $id, string $select = '*') { |
||
419 | return $this->db->select($select,'users',"id_user='$id'",'LIMIT 1'); |
||
420 | } |
||
421 | |||
422 | /** |
||
423 | * Obtiene a todos los usuarios |
||
424 | * |
||
425 | * @param string $select : Por defecto es *, se usa para obtener sólo los parámetros necesarios |
||
426 | * |
||
427 | * @return false|array con información de los usuarios |
||
428 | */ |
||
429 | public function getUsers(string $select = '*') { |
||
430 | return $this->db->select($select,'users'); |
||
431 | } |
||
432 | |||
433 | /** |
||
434 | * Obtiene datos del usuario conectado actualmente |
||
435 | * |
||
436 | * @param string $select : Por defecto es *, se usa para obtener sólo los parámetros necesarios |
||
437 | * |
||
438 | * @throws ModelsException si el usuario no está logeado |
||
439 | * @return array con datos del usuario conectado |
||
440 | */ |
||
441 | public function getOwnerUser(string $select = '*') : array { |
||
442 | if(null !== $this->id_user) { |
||
443 | |||
444 | $user = $this->db->select($select,'users',"id_user='$this->id_user'",'LIMIT 1'); |
||
445 | |||
446 | # Si se borra al usuario desde la base de datos y sigue con la sesión activa |
||
447 | if(false === $user) { |
||
448 | $this->logout(); |
||
449 | } |
||
450 | |||
451 | return $user[0]; |
||
452 | } |
||
453 | |||
454 | throw new \RuntimeException('El usuario no está logeado.'); |
||
455 | } |
||
456 | |||
457 | /** |
||
458 | * Instala el módulo de usuarios en la base de datos para que pueda funcionar correctamete. |
||
459 | * |
||
460 | * @throws \RuntimeException si no se puede realizar la query |
||
461 | */ |
||
462 | public function install() { |
||
463 | if (!$this->db->query(" |
||
464 | CREATE TABLE IF NOT EXISTS `users` ( |
||
465 | `id_user` int(11) UNSIGNED NOT NULL AUTO_INCREMENT, |
||
466 | `name` varchar(100) NOT NULL, |
||
467 | `email` varchar(150) NOT NULL, |
||
468 | `pass` varchar(90) NOT NULL, |
||
469 | `tmp_pass` varchar(90) NOT NULL DEFAULT '', |
||
470 | `token` varchar(90) NOT NULL DEFAULT '', |
||
471 | PRIMARY KEY (`id_user`) |
||
472 | ) ENGINE=InnoDB DEFAULT CHARSET=latin1; |
||
473 | ")) { |
||
474 | throw new \RuntimeException('No se ha podido instalar el módulo de usuarios.'); |
||
475 | } |
||
476 | |||
477 | dump('Módulo instalado correctamente, el método <b>(new Model\Users)->install()</b> puede ser borrado.'); |
||
478 | exit(1); |
||
479 | } |
||
480 | |||
481 | /** |
||
482 | * __construct() |
||
483 | */ |
||
484 | public function __construct(IRouter $router = null) { |
||
485 | parent::__construct($router); |
||
486 | $this->startDBConexion(); |
||
487 | } |
||
488 | |||
489 | /** |
||
490 | * __destruct() |
||
491 | */ |
||
492 | public function __destruct() { |
||
493 | parent::__destruct(); |
||
494 | $this->endDBConexion(); |
||
495 | } |
||
496 | |||
497 | } |
||
498 |
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.